commit 0682c7958067c30b7c78d817d94ff07e61e5c696 Author: Funky (OpenClaw) Date: Mon Feb 23 03:42:22 2026 +0000 Initial infrastructure documentation - comprehensive homelab reference diff --git a/.claude-context.md b/.claude-context.md new file mode 100644 index 0000000..089555d --- /dev/null +++ b/.claude-context.md @@ -0,0 +1,280 @@ +# Fred's Projects - Claude Code Context +**Source of Truth**: `C:\Users\Fred\projects` + +This file provides context to Claude Code about all of Fred's active projects. When Fred starts a Claude Code session in VS Code Insiders, Claude should be aware of this ecosystem. + +--- + +## Active Projects + +### 1. claude-workflows +**Path**: `C:\Users\Fred\projects\claude-workflows` +**Purpose**: Shared slash commands and ADHD-friendly productivity tools for Claude Code +**Status**: Active development +**Key Features**: +- `/push` - Quick commit and push +- `/eod` - End of day workflow +- ADHD assistant system with proactive interventions +- Auto-discovery scripts for cross-project setup +- Personality-driven assistant behavior (`personality.md`) + +**Context Files**: +- `.assistant/personality.md` - Defines ADHD-friendly behavior rules +- `.assistant/state.json.template` - Session state tracking template + +--- + +### 2. VA-Strategy +**Path**: `C:\Users\Fred\projects\VA-Strategy` +**Purpose**: Personal VA disability claims management system +**Status**: Active - in progress +**Goal**: 100% VA disability rating via TDIU +**Current Rating**: 60% combined, 30% highest single (PTSD) + +**Context Files**: +- `CLAUDE.md` - Project-specific guidance for Claude Code +- `Gemini.md` - Master strategic roadmap +- `tracking/master-tracking.md` - Claim status tracker +- `tracking/immediate-action-checklist.md` - Priority to-do list + +**Key Workflows**: +- Headache tracking for migraine claim (50% target) +- PTSD statement preparation (70% target) +- Sleep apnea evidence collection (50% target) +- Git-based document versioning + +--- + +### 3. infrastructure +**Path**: `C:\Users\Fred\projects\infrastructure` +**Purpose**: Home network, Home Assistant, smart home automation +**Status**: Active maintenance + active projects + +**Active Subprojects**: +- **Voice Assistant**: Local GPU-accelerated voice system (Gaming PC + Surface Go) +- **Furnace Control**: ESP32-based smart furnace controller (planning phase) +- **Home Assistant**: Main HA configuration +- **ESPHome**: Device configurations (garage controller, planned furnace) + +**Context Files**: +- `README.md` - Infrastructure overview +- `docs/FURNACE-PROJECT.md` - ESP32 furnace project +- `voice-assistant/CLAUDE.md` - Voice system context + +**Tech Stack**: Home Assistant, ESPHome, MQTT, Docker, Ollama, Whisper, Piper TTS + +--- + +### 4. claude-code-history +**Path**: `C:\Users\Fred\projects\claude-code-history` +**Purpose**: Session history and state persistence for Claude Code +**Status**: Background system + +Contains: +- Session transcripts +- State files +- Project history +- Stats cache + +--- + +### 5. config +**Path**: `C:\Users\Fred\projects\config` +**Purpose**: Shared configuration files +**Status**: Minimal/placeholder + +--- + +## ADHD Assistant Behavior + +Claude Code should operate with ADHD-friendly principles when working with Fred: + +### Core Principles +1. **Proactive, Not Reactive** - Notice patterns and intervene +2. **Gentle Nudging** - Suggest, don't command +3. **Celebrate Wins** - Acknowledge all completions +4. **Context Preservation** - Remember across sessions +5. **No Judgment** - Side quests are valid exploration + +### Side Quest Detection +**When Fred starts working on something unrelated to the current project**, Claude should: + +``` +๐Ÿค” I notice we've shifted focus: + + Current project: [X] + New work: [Y] + + This looks like a side quest. Would you like to: + 1. Continue (I'll track it) + 2. Switch to the [Y] project + 3. Make this a new project + 4. Park it and return to [X] +``` + +### Project-Specific Context Loading +When Fred opens a project in VS Code, Claude should: +1. Check for project-specific `CLAUDE.md` file +2. Load project context and current status +3. Greet with relevant session info +4. Track scope drift across project boundaries + +--- + +## Shared Resources + +### Claude Shared Directory +**Path**: `C:\Users\Fred\claude-shared\` (symlinked from claude-workflows) +**Contains**: +- Shared slash commands +- ADHD assistant state file (`~/.claude-assistant/state.json`) +- Setup scripts for auto-discovery + +### Slash Commands (Available Everywhere) +- `/push` - Auto-commit and push +- `/eod` - End of day workflow +- `/focus` - Check current goals *(in development)* +- `/sidequest` - Log tangents *(in development)* +- `/stuck` - Get unstuck *(in development)* +- `/reflect` - Session review *(in development)* + +--- + +## Cross-Project Workflows + +### When Fred Starts a Side Quest +Example: Working on VA-Strategy, starts researching ESPHome for furnace + +**Claude should**: +1. Detect context shift (VA โ†’ infrastructure) +2. Offer to switch projects +3. If continuing, track as side quest in state file +4. Set timer for check-in (30 min default) +5. Preserve VA-Strategy context for return + +### When Fred Opens VS Code in a Project +**Claude should**: +1. Read `.claude-context.md` (this file) for ecosystem awareness +2. Read project-specific `CLAUDE.md` if exists +3. Check state file for active session +4. Greet with context: + ``` + ๐Ÿ“‹ Welcome back, Fred! + + Project: [name] + Last session: [time ago] + Status: [brief summary] + + Ready to continue? + ``` + +--- + +## State Management + +### Session State File +**Location**: `~/.claude-assistant/state.json` (Windows: `C:\Users\Fred\.claude-assistant\state.json`) + +**Tracks**: +- Current project and goal +- Active side quests +- Stuck signals +- Session history +- Cross-project context + +### State File Schema +```json +{ + "current_session": { + "project": "VA-Strategy", + "started_at": "2025-12-13T10:00:00Z", + "primary_goal": "Complete headache log entries", + "side_quests": [ + { + "topic": "Research ESP32 temperature sensors", + "original_project": "VA-Strategy", + "target_project": "infrastructure", + "started_at": "2025-12-13T10:30:00Z", + "status": "in_progress" + } + ] + } +} +``` + +--- + +## VS Code Insiders Setup + +### Recommended Settings +To enable full context awareness in VS Code Insiders: + +1. **Multi-root Workspace** - Open all projects simultaneously +2. **Workspace-Specific Settings** - Per-project `.vscode/settings.json` +3. **Claude Context Files** - This file + project-specific `CLAUDE.md` files + +### Creating Multi-Root Workspace +```json +{ + "folders": [ + { + "path": "C:\\Users\\Fred\\projects\\claude-workflows", + "name": "๐ŸŽฏ Claude Workflows" + }, + { + "path": "C:\\Users\\Fred\\projects\\VA-Strategy", + "name": "๐Ÿฅ VA Strategy" + }, + { + "path": "C:\\Users\\Fred\\projects\\infrastructure", + "name": "๐Ÿ  Infrastructure" + } + ], + "settings": { + "claude.contextFiles": [ + "C:\\Users\\Fred\\projects\\.claude-context.md" + ] + } +} +``` + +Save as: `C:\Users\Fred\projects\fred-workspace.code-workspace` + +--- + +## Quick Reference + +### Project Quick IDs +- **claude-workflows**: Productivity tools, ADHD assistant +- **VA-Strategy**: VA claims, documentation, tracking +- **infrastructure**: Home automation, voice assistant, ESPHome +- **claude-code-history**: Session history (background) +- **config**: Shared configs (minimal) + +### When to Switch Projects +| You're Talking About... | Project | Action | +|------------------------|---------|--------| +| Slash commands, ADHD features | claude-workflows | Switch or side quest | +| VA claims, medical evidence | VA-Strategy | Switch or side quest | +| Home Assistant, ESP32, voice assistant | infrastructure | Switch or side quest | +| New idea unrelated to current work | TBD | Offer to create new project | + +--- + +## For Claude Code: Session Start Checklist + +When Fred starts VS Code Insiders: + +- [ ] Read `.claude-context.md` (this file) +- [ ] Identify current project from workspace/folder +- [ ] Read project-specific `CLAUDE.md` if exists +- [ ] Check `~/.claude-assistant/state.json` for active session +- [ ] Greet with relevant context +- [ ] Be ready to detect and manage side quests +- [ ] Apply ADHD-friendly behavior from `personality.md` + +--- + +**Last Updated**: 2025-12-13 +**Maintained By**: Fred with Claude Code assistance +**Purpose**: Provide ecosystem-level context for intelligent, ADHD-friendly Claude Code sessions diff --git a/.claude/index.html b/.claude/index.html new file mode 100644 index 0000000..2796f69 --- /dev/null +++ b/.claude/index.html @@ -0,0 +1,17 @@ + + + + +Directory listing for /.claude/ + + +

Directory listing for /.claude/

+
+ +
+ + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a4be0e0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# Ignore embedded git repositories (manage separately) +VA-Strategy/ +claude-code-history/ +claude-workflows/ +config/ + +# Windows +Thumbs.db +Desktop.ini + +# Temp files +*.tmp +*.temp +nul +.aider* diff --git a/AIDER-QUICKSTART.md b/AIDER-QUICKSTART.md new file mode 100644 index 0000000..eaf2db5 --- /dev/null +++ b/AIDER-QUICKSTART.md @@ -0,0 +1,174 @@ +# Aider Quick Start Guide + +## What is Aider? + +Aider is your **local, free AI coding assistant** that works with Ollama. It saves Claude API tokens for routine coding tasks. + +## Setup Complete! โœ“ + +- Aider installed +- Ollama configured with two models: + - `qwen2.5-coder:7b-instruct` (fast, 8GB VRAM) + - `qwen2.5-coder:14b-instruct` (smart, uses some RAM) +- Config file: `~/.aider.conf.yml` +- Helper functions: `C:\Users\Fred\projects\aider-helpers.ps1` + +## Quick Start + +### 1. Load Helper Functions (One Time Setup) + +Add this line to your PowerShell profile: + +```powershell +. C:\Users\Fred\projects\aider-helpers.ps1 +``` + +To find your profile location: +```powershell +$PROFILE +``` + +Or run the helpers manually each session: +```powershell +. C:\Users\Fred\projects\aider-helpers.ps1 +``` + +### 2. Basic Usage + +```powershell +# Navigate to your project +cd C:\Users\Fred\projects\VA-Strategy + +# Start Aider (uses 7b model by default) +aider-fast + +# Or use the smarter model for complex tasks +aider-smart + +# Or use plain aider (reads .aider.conf.yml) +aider +``` + +### 3. Inside Aider + +``` +> Add a function to parse headache log entries +> Refactor the PTSD statement generator +> Add error handling to the tracking module +> /help # see all commands +> /exit # quit +``` + +## When to Use What + +### Use Aider (Local) for: +- โœ… Refactoring code +- โœ… Writing boilerplate +- โœ… Adding simple features +- โœ… Code reviews +- โœ… Fixing simple bugs +- โœ… Documentation +- โœ… Test writing + +Cost: **$0** (completely free, uses your GPU) + +### Use Claude Code for: +- ๐ŸŽฏ Complex architectural decisions +- ๐ŸŽฏ Multi-file refactors requiring deep understanding +- ๐ŸŽฏ Difficult debugging +- ๐ŸŽฏ Planning new features +- ๐ŸŽฏ Strategic code design + +Cost: ~$3-15 per million tokens (monthly limit) + +## Helper Commands + +```powershell +aider-fast # Fast 7b model (everyday coding) +aider-smart # Powerful 14b model (complex tasks) +aider-plan # Architect mode (planning) +aider-commit # Generate git commit messages +aider-watch # Auto-reload on file changes +aider-status # Show available models and commands +aider-estimate # Compare token costs +``` + +## Examples + +### Example 1: Simple Refactor +```powershell +cd C:\Users\Fred\projects\VA-Strategy +aider-fast tracking/headache-log.py + +> Refactor this to use dataclasses instead of dictionaries +``` + +### Example 2: Add Feature +```powershell +cd C:\Users\Fred\projects\infrastructure\voice-assistant +aider-fast + +> Add a new command to check system temperature +> /add sensors.py utils.py +> Make sure it logs to the debug file +``` + +### Example 3: Git Commit +```powershell +git add . +aider-commit +# Aider will analyze changes and suggest a commit message +``` + +## Tips + +1. **Start Small**: Try Aider on simple tasks first to get comfortable +2. **Use Git**: Aider works best with git repos (can auto-commit) +3. **Be Specific**: Clear prompts get better results ("Add error handling for missing files" vs "make it better") +4. **Switch Models**: Use 7b for speed, 14b for quality +5. **Save Claude Tokens**: Use Aider for 80% of tasks, Claude for the 20% that need genius-level reasoning + +## Token Savings Example + +**Typical Day:** +- 10 simple refactors: Aider (free) instead of Claude ($0.30) +- 5 feature additions: Aider (free) instead of Claude ($0.75) +- 3 bug fixes: Aider (free) instead of Claude ($0.45) +- 2 complex architecture tasks: Claude ($0.60) + +**Total saved: $1.50/day = $45/month** + +## Troubleshooting + +### Ollama not running +```powershell +# Check Ollama status +ollama list + +# Restart Ollama if needed (it should auto-start) +``` + +### Model too slow +```powershell +# Switch to faster 7b model +aider --model ollama/qwen2.5-coder:7b-instruct +``` + +### Need better quality +```powershell +# Switch to smarter 14b model +aider --model ollama/qwen2.5-coder:14b-instruct +``` + +## Next Steps + +1. Load the helper functions in your PowerShell profile +2. Try `aider-status` to verify everything works +3. Navigate to a project and run `aider-fast` +4. Start with a simple task like "Add a docstring to this function" + +--- + +**Happy coding!** You're now set up to save Claude tokens while maintaining productivity. + +For full Aider documentation: https://aider.chat diff --git a/ALERT-REDUCTION-SUMMARY.md b/ALERT-REDUCTION-SUMMARY.md new file mode 100644 index 0000000..a604475 --- /dev/null +++ b/ALERT-REDUCTION-SUMMARY.md @@ -0,0 +1,190 @@ +# Alert Reduction Summary + +## What Changed + +Your Prometheus alerting has been updated to **drastically reduce notification noise** while still catching critical issues. + +### Before (Your Current Inbox) +- ๐Ÿ”ฅ **All warnings** โ†’ Email inbox spam +- Multiple alerts per minute +- Drowning out important messages + +### After (New Configuration) +- โœ… **Only CRITICAL alerts** โ†’ Discord notification +- **WARNING alerts** โ†’ Logged in Prometheus UI (no notification) +- Clean inbox, important alerts still get through + +--- + +## Updated Alert Thresholds + +### CPU Monitoring +| Severity | Threshold | Duration | Action | +|----------|-----------|----------|--------| +| **WARNING** | 80%+ | 5 minutes | Logged only | +| **CRITICAL** | 95%+ | 5 minutes | **Discord notification** | + +### Memory Monitoring +| Severity | Threshold | Duration | Action | +|----------|-----------|----------|--------| +| **WARNING** | 85%+ | 10 minutes | Logged only | +| **CRITICAL** | 95%+ | 5 minutes | **Discord notification** | + +### Disk Space +| Severity | Threshold | Duration | Action | +|----------|-----------|----------|--------| +| **WARNING** | <15% free | 5 minutes | Logged only | +| **CRITICAL** | <5% free | 2 minutes | **Discord notification** | + +--- + +## CRITICAL Alerts (Discord Notifications) + +You will **ONLY** receive Discord notifications for: + +### Infrastructure +- โŒ **HostDown** - Any host completely unreachable for 2+ minutes +- โŒ **ProxmoxNodeDown** - Proxmox host down for 2+ minutes +- โŒ **VPSDown** - VPS (66.63.182.168) unreachable for 2+ minutes + +### Performance +- ๐Ÿ”ฅ **CriticalCPUUsage** - CPU >95% sustained for 5+ minutes +- ๐Ÿง  **CriticalMemoryUsage** - Memory >95% sustained for 5+ minutes + +### Storage +- ๐Ÿ’พ **DiskSpaceCritical** - Disk <5% free space for 2+ minutes + +### Services +- ๐Ÿ—„๏ธ **PostgreSQLDown** - Database down for 2+ minutes +- โš™๏ธ **PrometheusConfigReloadFailed** - Monitoring system config broken + +--- + +## WARNING Alerts (Logged Only) + +These alerts are **visible in Prometheus UI** but **do NOT trigger notifications**: + +- CPU 80-95% (warning threshold) +- Memory 85-95% +- Disk 5-15% free +- Network interface down +- High disk I/O wait +- Home Assistant down +- n8n automation down +- Prometheus scrape failures +- and more... + +**You can check them anytime at:** http://10.0.10.25:9090/alerts + +--- + +## Deployment + +### Quick Deploy +```bash +cd /root/.openclaw/workspace/fred-infrastructure +./deploy-reduced-alerts.sh +``` + +### Manual Steps (if needed) +```bash +# 1. Backup existing configs +ssh root@10.0.10.25 'mkdir -p /etc/prometheus/backups' +ssh root@10.0.10.25 'cp /etc/prometheus/alertmanager.yml /etc/prometheus/backups/alertmanager.yml.backup' +ssh root@10.0.10.25 'cp /etc/prometheus/rules/homelab-alerts.yml /etc/prometheus/backups/homelab-alerts.yml.backup' + +# 2. Upload new configs +scp alertmanager-config-updated.yml root@10.0.10.25:/etc/prometheus/alertmanager.yml +scp prometheus-alert-rules-updated.yml root@10.0.10.25:/etc/prometheus/rules/homelab-alerts.yml + +# 3. Reload services +ssh root@10.0.10.25 'systemctl reload prometheus' +ssh root@10.0.10.25 'systemctl reload prometheus-alertmanager' + +# 4. Verify +curl http://10.0.10.25:9090/api/v1/rules +curl http://10.0.10.25:9093/api/v1/status +``` + +### Test Critical Alert +Send a test alert to verify Discord webhook works: +```bash +curl -X POST http://10.0.10.25:9093/api/v1/alerts -d '[ + { + "labels": { + "alertname": "TestCriticalAlert", + "severity": "critical", + "instance": "test:9100" + }, + "annotations": { + "summary": "Test alert - please ignore" + } + } +]' +``` + +You should see this appear in Discord within ~30 seconds. + +--- + +## Expected Results + +### Your Email Inbox +- **Before:** 50+ Prometheus alerts per day +- **After:** **ZERO** (all notifications moved to Discord) + +### Your Discord Server +- **Only critical issues** that need immediate attention +- Estimated: 0-2 alerts per day (unless something is actually broken) + +### Prometheus UI +- **All alerts still visible** at http://10.0.10.25:9090/alerts +- Use this to check warnings when convenient + +--- + +## Monitoring Your Alerts + +### Check Active Alerts +```bash +# View current alerts +curl http://10.0.10.25:9090/api/v1/alerts | python3 -m json.tool + +# View Alertmanager status +curl http://10.0.10.25:9093/api/v1/status | python3 -m json.tool +``` + +### Web Interfaces +- **Prometheus:** http://10.0.10.25:9090/alerts +- **Alertmanager:** http://10.0.10.25:9093/#/alerts + +--- + +## Rollback (If Needed) + +If something goes wrong, restore the backup: +```bash +ssh root@10.0.10.25 'cp /etc/prometheus/backups/alertmanager.yml.* /etc/prometheus/alertmanager.yml' +ssh root@10.0.10.25 'cp /etc/prometheus/backups/homelab-alerts.yml.* /etc/prometheus/rules/homelab-alerts.yml' +ssh root@10.0.10.25 'systemctl reload prometheus prometheus-alertmanager' +``` + +--- + +## Files Created + +- `prometheus-alert-rules-updated.yml` - Updated alert rules (CPU 80%+, warnings logged) +- `alertmanager-config-updated.yml` - Only critical โ†’ Discord, warnings โ†’ null +- `deploy-reduced-alerts.sh` - Automated deployment script +- `ALERT-REDUCTION-SUMMARY.md` - This file + +--- + +## Questions? + +- **"I want to see warnings occasionally"** โ†’ Check http://10.0.10.25:9090/alerts daily +- **"I need email back"** โ†’ We can add it back for critical-only +- **"80% CPU is too low"** โ†’ We can adjust the threshold up/down +- **"I want alerts in Slack instead"** โ†’ Easy to add another webhook + +Let me know if you want to tweak anything! ๐ŸŽฏ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..5272064 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,59 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Quick Reference + +This repository manages Fred's homelab infrastructure - a mix of deployment scripts, monitoring tools, and service integrations for a Proxmox-based network. + +**When you need specific information, refer to these specialized docs:** + +### Core Architecture + +- [.claude/docs/ARCHITECTURE.md](.claude/docs/ARCHITECTURE.md) - Infrastructure components, network topology, WireGuard VPN +- [.claude/docs/SERVICES.md](.claude/docs/SERVICES.md) - Deployed services, container IDs, service-specific details +- [.claude/docs/SCRIPTS.md](.claude/docs/SCRIPTS.md) - Deployment scripts and automation tools + +### Operations + +- [.claude/docs/COMMON-TASKS.md](.claude/docs/COMMON-TASKS.md) - SSH access, Caddy management, container operations +- [.claude/docs/CERTIFICATES.md](.claude/docs/CERTIFICATES.md) - Step-CA setup, ACME provisioner, SSL configuration + +### Detailed References (load when needed) + +- Subdirectories have their own CLAUDE.md files (infrastructure/, n8n-workflows/, etc.) +- Service-specific deployment guides in respective directories +- Submodule documentation in submodule directories + +## Essential Quick Facts + +**Primary Infrastructure:** + +- VPS: fred@66.63.182.168 (vps.nianticbooks.com) - Caddy reverse proxy +- Proxmox: root@10.0.10.3 (main-pve) - LXC container host +- Network: 10.0.10.0/24 (WireGuard tunnel: 10.0.8.0/24) + +**Critical Services:** + +- Uptime Kuma: 10.0.10.26 (CT 128) +- Home Assistant: 10.0.10.24 +- CA Server: 10.0.10.15 (CT 115) + +## Repository Structure + +**Subdirectories:** + +- `infrastructure/` - Core infrastructure docs and automation +- `n8n-workflows/` - n8n workflow JSON definitions +- `bible-reading-plan/` - Submodule (separate repo) + +**Slash Commands:** + +- `/eod`, `/push`, `/focus`, `/sidequest`, `/stuck`, `/reflect` (in `.claude/commands/`) + +## Key Constraints + +- Proxmox storage: `local` only (never `local-lvm`) +- SSH: Always use key-based auth +- Test on target system before committing +- VPS: 2 CPU / 4GB RAM - lightweight services only diff --git a/C๏€บUsersFredDownloadscaddy-internal-root-ca.crt b/C๏€บUsersFredDownloadscaddy-internal-root-ca.crt new file mode 100644 index 0000000..895ed4a --- /dev/null +++ b/C๏€บUsersFredDownloadscaddy-internal-root-ca.crt @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBozCCAUmgAwIBAgIQDhQK0OV98/81eUmxrmQDODAKBggqhkjOPQQDAjAwMS4w +LAYDVQQDEyVDYWRkeSBMb2NhbCBBdXRob3JpdHkgLSAyMDI2IEVDQyBSb290MB4X +DTI2MDEyNTE5Mjg0MloXDTM1MTIwNDE5Mjg0MlowMDEuMCwGA1UEAxMlQ2FkZHkg +TG9jYWwgQXV0aG9yaXR5IC0gMjAyNiBFQ0MgUm9vdDBZMBMGByqGSM49AgEGCCqG +SM49AwEHA0IABK+03Zxz5H6c6/5NPoRsDbR50mSiYWitxjSgu8WVHi9QKrhRUDcP +9xulfLgnDEGKzg6PHP8uSKmKjJ+S94c633+jRTBDMA4GA1UdDwEB/wQEAwIBBjAS +BgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBTa7eOocuWd3U3qq0Upi55X/VrC +PjAKBggqhkjOPQQDAgNIADBFAiA0aW0FfQ3KRZ5ojp3oK5wwBSmjaRNQko7qtXw1 +PZrlEgIhANpvOrrYzoBCEm/anqkOmTL3qf+wFNFyg+hAl3L70O/f +-----END CERTIFICATE----- diff --git a/DEPLOY-NOW.md b/DEPLOY-NOW.md new file mode 100644 index 0000000..c826d29 --- /dev/null +++ b/DEPLOY-NOW.md @@ -0,0 +1,138 @@ +# ๐Ÿš€ DEPLOY NOW - Stop the Alert Spam! + +## Quick Deploy (Copy/Paste This) + +Run these commands from **your terminal** (PC, not OpenClaw): + +### 1. Access the Prometheus container +```bash +# SSH to Proxmox +ssh root@10.0.10.3 + +# Enter the Prometheus container +pct enter 125 +``` + +### 2. Backup existing configs +```bash +# Inside the container +mkdir -p /etc/prometheus/backups +cp /etc/prometheus/alertmanager.yml /etc/prometheus/backups/alertmanager.yml.backup +cp /etc/prometheus/rules/homelab-alerts.yml /etc/prometheus/backups/homelab-alerts.yml.backup +``` + +### 3. Download the new configs +From your **PC**, download the updated files I created: +```bash +# On your PC +scp root@10.0.10.28:/root/.openclaw/workspace/fred-infrastructure/alertmanager-config-updated.yml ~/ +scp root@10.0.10.28:/root/.openclaw/workspace/fred-infrastructure/prometheus-alert-rules-updated.yml ~/ +``` + +### 4. Upload to Prometheus container +```bash +# On your PC +scp ~/alertmanager-config-updated.yml root@10.0.10.3:/tmp/ +scp ~/prometheus-alert-rules-updated.yml root@10.0.10.3:/tmp/ +``` + +Then back in Proxmox SSH: +```bash +# Copy into container +pct push 125 /tmp/alertmanager-config-updated.yml /etc/prometheus/alertmanager.yml +pct push 125 /tmp/prometheus-alert-rules-updated.yml /etc/prometheus/rules/homelab-alerts.yml +``` + +### 5. Reload Prometheus +```bash +# Inside the container (pct enter 125) +systemctl reload prometheus +systemctl reload prometheus-alertmanager + +# Verify services reloaded +systemctl status prometheus +systemctl status prometheus-alertmanager +``` + +### 6. Test it works +```bash +# Should see the new config +curl http://10.0.10.25:9090/api/v1/rules + +# Send test alert to Discord +curl -X POST http://10.0.10.25:9093/api/v1/alerts -d '[ + { + "labels": { + "alertname": "TestCriticalAlert", + "severity": "critical", + "instance": "test:9100" + }, + "annotations": { + "summary": "Test alert - please ignore" + } + } +]' +``` + +You should see the test alert appear in **Discord** within 30 seconds! + +--- + +## โšก EVEN FASTER: One-Liner (Advanced) + +If you want to do it all in one go: +```bash +ssh root@10.0.10.3 "pct exec 125 -- bash -c ' + mkdir -p /etc/prometheus/backups && \ + cp /etc/prometheus/alertmanager.yml /etc/prometheus/backups/alertmanager.yml.backup && \ + cp /etc/prometheus/rules/homelab-alerts.yml /etc/prometheus/backups/homelab-alerts.yml.backup && \ + curl -o /etc/prometheus/alertmanager.yml http://10.0.10.28:PORT/workspace/fred-infrastructure/alertmanager-config-updated.yml && \ + curl -o /etc/prometheus/rules/homelab-alerts.yml http://10.0.10.28:PORT/workspace/fred-infrastructure/prometheus-alert-rules-updated.yml && \ + systemctl reload prometheus prometheus-alertmanager +'" +``` + +(Replace `PORT` with OpenClaw web port if you have file serving enabled) + +--- + +## ๐ŸŽฏ What This Does + +**Before:** +- โš ๏ธ WARNING alerts every 2 minutes โ†’ Email +- "CPU changed 61.5% rapidly (5.04 โ†’ 1.94)" โ† WTF is this even + +**After:** +- โœ… CRITICAL alerts only โ†’ Discord +- โŒ WARNING alerts โ†’ Logged, not sent +- ๐Ÿ“ง Email inbox โ†’ CLEAN + +**Result:** +- Your inbox goes from 50+ alerts/day to ZERO +- Discord gets only real emergencies (host down, disk full, etc.) +- Warnings still logged in Prometheus UI if you want to check + +--- + +## ๐Ÿšจ Current Alert Spam Example + +That alert you just got: +``` +โš ๏ธ WARNING: 10.0.10.3:9100 +cpu_usage on 10.0.10.3:9100 changed 61.5% rapidly (5.04 โ†’ 1.94) +``` + +This is **literally complaining that CPU usage went DOWN**. Pure noise. + +After deployment: **This will NEVER notify you again.** It'll be logged if you want to see it, but won't spam your inbox. + +--- + +## Questions? + +- **"I can't SSH to Proxmox"** โ†’ Let me know, I'll help +- **"It broke something"** โ†’ Restore backup: `cp /etc/prometheus/backups/* /etc/prometheus/` +- **"I want it even quieter"** โ†’ We can tune thresholds further +- **"I want email back for criticals"** โ†’ Easy to add + +**Just deploy this and watch your inbox become peaceful again.** ๐Ÿง˜โ€โ™‚๏ธโœจ diff --git a/N8N-UPTIME-KUMA-GUIDE.md b/N8N-UPTIME-KUMA-GUIDE.md new file mode 100644 index 0000000..b928abe --- /dev/null +++ b/N8N-UPTIME-KUMA-GUIDE.md @@ -0,0 +1,317 @@ +# n8n + Uptime Kuma Integration Guide + +**n8n for Beginners:** This guide will walk you through setting up smart notifications for your infrastructure monitoring. + +--- + +## What This Does + +This n8n workflow receives alerts from Uptime Kuma and routes them intelligently: + +- **CRITICAL alerts** โ†’ Email + Discord/Slack (immediate) +- **PUBLIC alerts** โ†’ Discord (after retry threshold) +- **INTERNAL/METRICS/UTILITY** โ†’ Logged only (no spam) + +All alerts are logged for debugging and historical tracking. + +--- + +## Step 1: Import Workflow into n8n + +### 1.1 Access n8n +Go to http://10.0.10.22:5678 + +### 1.2 Import the Workflow +1. Click **Workflows** (left sidebar) +2. Click **Add Workflow** (top right) +3. Click the **โ‹ฎ** menu (top right) โ†’ **Import from File** +4. Select: `C:\Users\Fred\projects\n8n-uptime-kuma-workflow.json` +5. Click **Import** + +### 1.3 Activate the Workflow +1. The workflow editor will open +2. Toggle the **Active** switch (top right) to **ON** +3. You should see "Workflow activated" message + +--- + +## Step 2: Get Your Webhook URL + +### 2.1 Find the Webhook URL +1. In the workflow editor, click on the **"Webhook - Uptime Kuma"** node (first blue node) +2. Look for **Test URL** or **Production URL** +3. Copy the production URL - it should look like: + ``` + http://10.0.10.22:5678/webhook/uptime-kuma + ``` + +**Save this URL** - you'll need it for Uptime Kuma! + +--- + +## Step 3: Configure Uptime Kuma Notifications + +### 3.1 Create n8n Notification Channel +1. Go to Uptime Kuma: http://10.0.10.26:3001 +2. Click **Settings** (left sidebar) +3. Click **Notifications** tab +4. Click **Add New Notification** + +### 3.2 Configure Webhook +- **Notification Type:** Select **Webhook** +- **Friendly Name:** `n8n Alert Router` +- **POST URL:** `http://10.0.10.22:5678/webhook/uptime-kuma` +- **Content Type:** `application/json` +- **Notification Sound:** (your preference) +- Click **Test** to verify +- Click **Save** + +### 3.3 Assign to Monitors +Now assign this notification to your monitors: + +1. Go to **Dashboard** +2. Click on a **CRITICAL** monitor (e.g., "Proxmox - main-pve") +3. Click **Edit** +4. Scroll to **Notifications** +5. Select **n8n Alert Router** +6. **Apply on:** Select **When status is DOWN** +7. Click **Save** + +**Repeat for all CRITICAL monitors** (you can select multiple monitors and edit in bulk) + +--- + +## Step 4: Enable Notification Channels + +By default, all notification nodes are **disabled** (to prevent errors). Enable the ones you want to use: + +### Option A: Discord (Recommended for Beginners) + +**4.1 Create Discord Webhook:** +1. Open Discord +2. Go to your server โ†’ Settings โ†’ Integrations +3. Click **Webhooks** โ†’ **New Webhook** +4. Name it "Uptime Kuma Alerts" +5. Select channel (e.g., #monitoring) +6. Copy the **Webhook URL** + +**4.2 Configure in n8n:** +1. In the workflow, click **"Discord - CRITICAL Alert"** node +2. Click **Parameters** +3. Paste your Discord webhook URL +4. Toggle the node **Enabled** (remove checkmark from "Disabled") +5. Click **Save** (top right) + +**Repeat for "Discord - PUBLIC Alert"** if you want public service alerts too. + +### Option B: Email (SMTP) + +**4.1 Configure in n8n:** +1. Click **"Email - CRITICAL Alert"** node +2. Click **Credentials** โ†’ **Create New** +3. Choose your email provider: + - **Gmail:** Use App Password (not your main password) + - **Outlook/Office365:** Use SMTP settings + - **Custom SMTP:** Enter your SMTP details + +**Example: Gmail Setup** +- **From Email:** your-gmail@gmail.com +- **To Email:** your-alert-email@gmail.com +- **SMTP Host:** smtp.gmail.com +- **SMTP Port:** 587 +- **Username:** your-gmail@gmail.com +- **Password:** (App Password from Google Account settings) +- **Secure:** TLS + +4. Click **Test** to verify +5. Click **Save** +6. Enable the node (uncheck "Disabled") + +### Option C: Slack + +**4.1 Create Slack App:** +1. Go to https://api.slack.com/apps +2. Click **Create New App** โ†’ **From scratch** +3. Name: "Uptime Kuma" +4. Select your workspace +5. Go to **OAuth & Permissions** +6. Add Bot Token Scopes: `chat:write`, `chat:write.public` +7. Click **Install to Workspace** +8. Copy the **Bot User OAuth Token** + +**4.2 Configure in n8n:** +1. Click **"Slack - CRITICAL Alert"** node +2. Click **Credentials** โ†’ **Create New** +3. Paste your Bot Token +4. Click **Save** +5. Enable the node + +--- + +## Step 5: Test Your Setup + +### 5.1 Trigger a Test Alert +Let's test with a non-critical monitor: + +1. Go to Uptime Kuma +2. Find **"[UTILITY] CA Server"** monitor +3. Click **Edit** +4. Temporarily change port from `8443` to `9999` (invalid port) +5. Click **Save** +6. Wait 30-60 seconds for the monitor to check + +### 5.2 Check n8n Execution +1. Go to n8n: http://10.0.10.22:5678 +2. Click **Executions** (left sidebar) +3. You should see a new execution +4. Click it to view details + +### 5.3 Verify Alert Received +- **Discord:** Check your #monitoring channel +- **Email:** Check your inbox +- **Slack:** Check your channel + +### 5.4 Fix the Monitor +1. Go back to Uptime Kuma +2. Edit the CA Server monitor +3. Change port back to `8443` +4. Save + +--- + +## Workflow Overview + +Here's what happens when a monitor goes down: + +``` +Uptime Kuma Alert + โ†“ + n8n Webhook Receives + โ†“ + Format Alert Data + (Parses monitor info, status, error) + โ†“ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ†“ โ†“ โ†“ +Is CRITICAL? Is PUBLIC? Log All + โ†“ โ†“ Alerts + โ†“ โ†“ +Email + Discord +Discord + (Optional) +Slack +(Optional) +``` + +**Smart Routing:** +- CRITICAL monitors (VPS, Proxmox, PostgreSQL) โ†’ Immediate multi-channel alerts +- PUBLIC monitors (websites) โ†’ Discord only +- Everything else โ†’ Logged for review + +--- + +## Customization Ideas + +### Add SMS Alerts (via Twilio) +1. Add **Twilio** node after "Is CRITICAL?" +2. Configure with your Twilio account +3. Send SMS for CRITICAL alerts only + +### Add Phone Call Alerts +1. Use **Twilio** or **VoIP.ms** node +2. Trigger phone calls for critical infrastructure failures +3. Only for CRITICAL monitors to avoid spam + +### Create Alert Batching +1. Add **Wait** node before Discord/Email +2. Collect multiple alerts for 5 minutes +3. Send one consolidated message + +### Add Status Page Integration +1. After "Format Alert Data", add **HTTP Request** node +2. POST to your status page API +3. Auto-update public status page + +### Store Alerts in Database +1. Add **PostgreSQL** node (connect to 10.0.10.20) +2. Create `alerts` table +3. Log all alerts for historical analysis + +--- + +## Troubleshooting + +### Webhook Not Receiving Data +- Check workflow is **Active** (toggle at top right) +- Verify webhook URL is correct in Uptime Kuma +- Check n8n executions for errors + +### Discord/Email Not Sending +- Make sure node is **Enabled** (not grayed out) +- Check credentials are configured +- Look at n8n execution details for error messages + +### Too Many Alerts +- Increase retry counts in Uptime Kuma monitors +- Change "Apply on" to only DOWN (not also UP) +- Add filtering logic in n8n (only alert after X failures) + +### Not Enough Alerts +- Check monitor has notification assigned +- Verify "Apply on" includes DOWN status +- Check n8n workflow is active + +--- + +## Advanced: Alert Suppression (Prevent Spam) + +Add this after "Format Alert Data" to suppress duplicate alerts: + +1. Add **Function** node +2. Code: +```javascript +// Suppress duplicate alerts within 15 minutes +const alertKey = `${$json.fullName}-${$json.status}`; +const lastAlert = $workflow.staticData[alertKey] || 0; +const now = Date.now(); + +if (now - lastAlert < 15 * 60 * 1000) { + // Alert sent less than 15 min ago - suppress + return []; +} + +// Send alert and remember timestamp +$workflow.staticData[alertKey] = now; +return [$input.item]; +``` + +This prevents getting the same alert repeatedly. + +--- + +## Next Steps + +1. โœ… Import workflow into n8n +2. โœ… Activate workflow and get webhook URL +3. โœ… Configure Uptime Kuma notification +4. โœ… Enable Discord/Email/Slack notifications +5. โœ… Test with a non-critical monitor +6. โœ… Assign notifications to all CRITICAL monitors +7. โธ๏ธ Customize alert routing (optional) +8. โธ๏ธ Add SMS/phone call alerts (optional) +9. โธ๏ธ Create alert dashboard in Grafana (optional) + +--- + +**n8n Dashboard:** http://10.0.10.22:5678 +**Uptime Kuma:** http://10.0.10.26:3001 +**Workflow File:** `C:\Users\Fred\projects\n8n-uptime-kuma-workflow.json` + +--- + +**Pro Tip:** Once you're comfortable with n8n, you can create workflows for: +- Auto-restarting failed services +- Creating GitHub issues when monitors fail +- Sending daily/weekly uptime reports +- Integration with your homelab automation + +Welcome to n8n! ๐ŸŽ‰ diff --git a/Printing/.claude/index.html b/Printing/.claude/index.html new file mode 100644 index 0000000..8e26675 --- /dev/null +++ b/Printing/.claude/index.html @@ -0,0 +1,15 @@ + + + + +Directory listing for /Printing/.claude/ + + +

Directory listing for /Printing/.claude/

+
+ +
+ + diff --git a/Printing/.claude/settings.local.json b/Printing/.claude/settings.local.json new file mode 100644 index 0000000..bea46dd --- /dev/null +++ b/Printing/.claude/settings.local.json @@ -0,0 +1,27 @@ +{ + "permissions": { + "allow": [ + "Bash(python generate_nameplates.py:*)", + "Bash(git add:*)", + "Bash(git init:*)", + "Bash(git remote add:*)", + "Bash(git fetch:*)", + "Bash(git commit:*)", + "Bash(git push:*)", + "Bash(git ls-remote:*)", + "Bash(done)", + "Bash(git pull:*)", + "Bash(python:*)", + "Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D 'name=\"\"Fred\"\"' -o \"TEST_Fred_raised_text.stl\" \"zipper_pull_raised_text_template.scad\")", + "Bash(ls:*)", + "Bash(head:*)", + "Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" \"zipper-pulls-raised-text/Ford-Fred (2).stl\" --export-format echo --o -)", + "Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" \"zipper-pulls-raised-text/111.stl\" --export-format echo --o -)", + "Bash(dir \"C:\\Users\\Fred\\.claude-worktrees\\Printing\\eager-bose\\Nameplates\\zipper-pulls-raised-text\" /T:W)", + "Bash(findstr:*)", + "Bash(powershell:*)" + ], + "deny": [], + "ask": [] + } +} diff --git a/Printing/.gitignore b/Printing/.gitignore new file mode 100644 index 0000000..6930c85 --- /dev/null +++ b/Printing/.gitignore @@ -0,0 +1,41 @@ +# Generated STL files +*.stl + +# Build artifacts +*.o +*.obj + +# Python cache +__pycache__/ +*.pyc +*.pyo +*.pyd +.Python +*.so + +# Virtual environments +venv/ +env/ +ENV/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS files +.DS_Store +Thumbs.db +desktop.ini + +# Node.js (if accidentally included) +node_modules/ +*.tar.xz +*.tar.gz + +# Temporary files +*~ +*.tmp +*.temp +test_output.stl diff --git a/Printing/CLAUDE.md b/Printing/CLAUDE.md new file mode 100644 index 0000000..93eed8d --- /dev/null +++ b/Printing/CLAUDE.md @@ -0,0 +1,453 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +A collection of 3D printing utilities for generating personalized STL files using **Python + OpenSCAD**. The repository contains two main subprojects: + +1. **Nameplates** - Batch nameplate and zipper pull generator +2. **Key caps** - Mechanical keyboard keycap scaling utilities + +All projects use OpenSCAD as the 3D modeling engine, invoked via Python scripts or standalone SCAD files. + +## Repository Structure + +``` +Printing/ +โ”œโ”€โ”€ Nameplates/ # Nameplate and zipper pull generators +โ”‚ โ”œโ”€โ”€ generate_nameplates.py # Batch nameplate generator +โ”‚ โ”œโ”€โ”€ generate_zipper_pulls.py # Batch zipper pull generator +โ”‚ โ”œโ”€โ”€ nameplate_template.scad # Nameplate template (38mm height, no hole) +โ”‚ โ”œโ”€โ”€ zipper_pull_template.scad # Zipper pull template (34mm height, 4mm hole) +โ”‚ โ”œโ”€โ”€ names.txt # Input names (one per line) +โ”‚ โ”œโ”€โ”€ output_stl/ # Generated nameplate STLs +โ”‚ โ”œโ”€โ”€ zipper-pulls/ # Generated zipper pull STLs +โ”‚ โ”œโ”€โ”€ Fordscript.ttf # Custom cursive font (must be installed) +โ”‚ โ””โ”€โ”€ CLAUDE.md # Detailed nameplate documentation +โ”‚ +โ””โ”€โ”€ Key caps/ # Keyboard keycap scaling + โ”œโ”€โ”€ Body5_scaled_FIXED.scad # Primary keycap scaling script + โ”œโ”€โ”€ Body5.stl # Original keycap model + โ”œโ”€โ”€ CLAUDE.md # Detailed keycap documentation + โ””โ”€โ”€ README_scaling.md # User guide for scaling workflow +``` + +## Critical Dependencies + +### OpenSCAD Installation +- **Path**: `C:\Program Files\OpenSCAD\openscad.exe` (hardcoded in Python scripts) +- **Download**: https://openscad.org/downloads.html +- **Update path** in scripts if installed elsewhere (see `generate_nameplates.py:36`, `generate_zipper_pulls.py:36`) + +### Font Requirements (Nameplates/Zipper Pulls Only) +- **Font**: Fordscript cursive font (`Nameplates/Fordscript.ttf`) +- **Installation**: Must be installed in Windows (right-click โ†’ Install for all users) +- **Critical**: OpenSCAD references fonts by system name, not file path +- **Verification**: After install, restart OpenSCAD before generating files + +## Key Commands + +### Nameplates +```bash +# Generate all nameplates (38mm height, no hole) +cd Nameplates +python generate_nameplates.py + +# Output: output_stl/*.stl +``` + +### Zipper Pulls +```bash +# Generate all zipper pulls (100mm ร— 34mm total, 4mm hole with 1mm clearance) +cd Nameplates +python generate_zipper_pulls.py + +# Output: zipper-pulls/*.stl +``` + +### Key Caps +```powershell +# Render scaled keycap (command line) +& "C:\Program Files\OpenSCAD\openscad.exe" -o "output.stl" "Body5_scaled_FIXED.scad" + +# Or: Open in OpenSCAD GUI, adjust parameters, press F6 to render, export STL +& "C:\Program Files\OpenSCAD\openscad.exe" "Body5_scaled_FIXED.scad" +``` + +### Manual OpenSCAD Testing +```powershell +# Test single nameplate +& "C:\Program Files\OpenSCAD\openscad.exe" -D 'name="TestName"' -o test_output.stl nameplate_template.scad + +# Test single zipper pull +& "C:\Program Files\OpenSCAD\openscad.exe" -D 'name="TestName"' -o test_output.stl zipper_pull_template.scad + +# Preview in GUI (edit parameters at top of file) +& "C:\Program Files\OpenSCAD\openscad.exe" nameplate_template.scad +``` + +## Architecture Overview + +### Nameplates vs Zipper Pulls + +Both use the same two-layer oval design (white base + blue top with engraved text) but differ in dimensions and features: + +| Feature | Nameplates | Zipper Pulls | +|---------|-----------|-------------| +| Template | `nameplate_template.scad` | `zipper_pull_template.scad` | +| Generator | `generate_nameplates.py` | `generate_zipper_pulls.py` | +| Total Size (Wร—H) | 100mm ร— 38mm | 100mm ร— 34mm | +| Blue Layer Size | 96mm ร— 34mm | 96mm ร— 30mm | +| White Base Offset | 2mm all sides | 2mm all sides | +| Font Size | 30mm | 13mm | +| Hole | None | 4mm diameter, left side | +| Hole Clearance | N/A | 1mm from white base edge | +| Output Directory | `output_stl/` | `zipper-pulls/` | +| Use Case | Display nameplates | Zipper/bag attachments | + +**Important**: These are separate templates and scripts. Modifying one does not affect the other. + +### Pipeline Architecture + +``` +names.txt + โ†“ +Python Generator Script (reads names, iterates) + โ†“ +OpenSCAD CLI (renders each name via template) + โ†“ +STL Files (output to respective directories) +``` + +**Key Pattern**: Python script calls OpenSCAD subprocess for each name: +```python +cmd = [ + r'C:\Program Files\OpenSCAD\openscad.exe', + '-D', f'name="{escaped_name}"', # Override parameter + '-o', output_file, # Output STL path + template_file # SCAD template +] +subprocess.run(cmd, capture_output=True, text=True, check=True) +``` + +### OpenSCAD Template Pattern + +All SCAD templates follow this structure: +```scad +// 1. Parameters (overridable via -D flag) +name = "NAME"; +oval_width = 100; +// ... more parameters + +// 2. Modules (reusable geometry functions) +module oval(width, height, depth) { ... } +module base_layer() { ... } +module top_layer() { ... } + +// 3. Assembly module +module nameplate() { + base_layer(); + top_layer(); +} + +// 4. Execution (builds the model) +nameplate(); +``` + +## Project-Specific Conventions + +### Name Sanitization +Python scripts convert names to safe filenames: +- Removes special characters (keeps alphanumeric, spaces, hyphens, underscores) +- Replaces spaces with underscores +- Escapes quotes for shell: `name.replace('"', '\\"')` +- Example: `"John Doe"` โ†’ `John_Doe.stl` + +### Two-Layer Design Philosophy +- **White base layer**: Structural foundation, extends beyond blue layer by `base_offset` (2mm) +- **Blue top layer**: Visual layer with engraved text +- **Text engraving**: Cuts completely through blue layer (`text_depth = top_thickness = 1mm`) to expose white base underneath +- **Result**: White text on blue background + +### Hole Positioning (Zipper Pulls) +Hole is positioned on left side with clearance from white base edge: +```scad +white_width = oval_width + base_offset*2; +hole_x = -(white_width/2) + (hole_diameter/2) + hole_clearance; +// For 96mm blue + 2mm offset = 100mm white, 4mm hole, 1mm clearance: +// hole_x = -50 + 2 + 1 = -47mm from center +``` + +**Critical**: +- `hole_clearance` must be sufficient to prevent hole from breaking through oval edge. Current value (1mm) tested and verified. +- Hole is positioned relative to **white base width**, not blue layer width, to ensure it stays within the white outline. + +## Common Workflows + +### Add New Names to Both Systems +1. Edit `Nameplates/names.txt`, add name on new line +2. Run both generators: + ```bash + cd Nameplates + python generate_nameplates.py # Creates nameplate + python generate_zipper_pulls.py # Creates zipper pull + ``` +3. Find STLs in `output_stl/` and `zipper-pulls/` respectively + +### Adjust Zipper Pull Hole Clearance +If hole is too close to edge or breaking through: +1. Edit `zipper_pull_template.scad`, line 17: + ```scad + hole_clearance = 1; // Increase this value + ``` +2. Regenerate all zipper pulls: + ```bash + python generate_zipper_pulls.py + ``` + +**Note**: Hole clearance is measured from the **white base edge**, not the blue layer edge. + +### Create Custom Dimension Variant +To create a new variant (e.g., smaller nameplates): +1. Copy existing template: `nameplate_template.scad` โ†’ `nameplate_small_template.scad` +2. Modify dimensions at top of new file +3. Copy generator script: `generate_nameplates.py` โ†’ `generate_nameplates_small.py` +4. Update generator to use new template and output directory: + ```python + template_file = "nameplate_small_template.scad" + output_dir = "output_stl_small" + ``` + +### Scale Keycaps for Different Tolerances +1. Open `Key caps/Body5_scaled_FIXED.scad` in OpenSCAD GUI +2. Adjust `body_scale_xy` parameter at top (default: 0.98 = 98%) + - Too tight: decrease to 0.97 (3% reduction) + - Too loose: increase to 0.99 (1% reduction) +3. Press `F5` for preview, `F6` for full render +4. Export via `File > Export > Export as STL` +5. Test print and iterate + +See `Key caps/README_scaling.md` for detailed keycap scaling workflow. + +## Troubleshooting + +### Font Not Found (Nameplates/Zipper Pulls) +**Symptom**: Text is missing or uses default font in generated STLs. + +**Fix**: +1. Install `Fordscript.ttf`: Right-click โ†’ "Install for all users" +2. Restart OpenSCAD if running +3. Test with manual command: + ```powershell + & "C:\Program Files\OpenSCAD\openscad.exe" -D 'name="Test"' -o test.stl nameplate_template.scad + ``` +4. Open `test.stl` in slicer and verify text appears + +### OpenSCAD Path Not Found +**Symptom**: `FileNotFoundError: OpenSCAD not found` + +**Fix**: Update hardcoded path in generator scripts: +```python +# In generate_nameplates.py and generate_zipper_pulls.py, line 36: +cmd = [ + r'C:\Program Files\OpenSCAD\openscad.exe', # โ† Update this path + # ... +] +``` + +### Zipper Pull Hole Breaking Through Edge +**Symptom**: Hole intersects with oval edge, causing weak point or break. + +**Fix**: Increase `hole_clearance` in `zipper_pull_template.scad`: +```scad +hole_clearance = 1.5; // Increase from 1mm to 1.5mm +``` +Then regenerate all zipper pulls. + +**Note**: Ensure hole clearance is measured from the white base edge (100mm total width), not the blue layer edge (96mm). + +### Keycap Stem Scaled Incorrectly +**Symptom**: Cherry MX stem doesn't fit switch after scaling. + +**Cause**: Using wrong SCAD file (non-FIXED version requires manual stem positioning). + +**Fix**: Always use `Body5_scaled_FIXED.scad` which uses centered import approach. The stem preservation region is automatically centered at origin. + +## Advanced Customization + +### Add Custom Parameter to Templates +To add a new parameter (e.g., border thickness): + +1. Add parameter to template: + ```scad + border_thickness = 0.5; // New parameter + ``` + +2. Use in geometry: + ```scad + module base_layer() { + difference() { + oval(oval_width, oval_height, base_thickness); + // Cut border groove + translate([0, 0, base_thickness - border_thickness]) + oval(oval_width - 4, oval_height - 4, border_thickness + 0.1); + } + } + ``` + +3. Pass from Python script (optional): + ```python + cmd = [ + r'C:\Program Files\OpenSCAD\openscad.exe', + '-D', f'name="{escaped_name}"', + '-D', f'border_thickness={thickness}', # Override parameter + '-o', output_file, + template_file + ] + ``` + +### Batch Generate Multiple Sizes +Modify generator script to loop over multiple configurations: + +```python +configs = [ + {"template": "nameplate_template.scad", "width": 100, "height": 38}, + {"template": "nameplate_template.scad", "width": 80, "height": 30}, + {"template": "nameplate_template.scad", "width": 60, "height": 23}, +] + +for name in names: + for config in configs: + output_file = f"{config['width']}x{config['height']}_{safe_name}.stl" + cmd = [ + r'C:\Program Files\OpenSCAD\openscad.exe', + '-D', f'name="{escaped_name}"', + '-D', f'oval_width={config["width"]}', + '-D', f'oval_height={config["height"]}', + '-o', output_file, + config["template"] + ] + subprocess.run(cmd, ...) +``` + +## Development Reference + +### OpenSCAD Command Line Flags +```powershell +# Override parameter +-D 'name="value"' + +# Output file +-o output.stl + +# Check syntax (no output) +--check + +# Verbose output +--verbose + +# Clear cache (useful when changing imports) +--clear-cache + +# Version info +--version +``` + +### Python Subprocess Pattern +All generators use the same pattern: +```python +result = subprocess.run( + cmd, + capture_output=True, # Capture stdout/stderr + text=True, # Return strings, not bytes + check=True # Raise CalledProcessError on non-zero exit +) +``` + +Error handling: +```python +try: + subprocess.run(cmd, capture_output=True, text=True, check=True) +except subprocess.CalledProcessError as e: + print(f"Error: {e.stderr}") +except FileNotFoundError: + print("OpenSCAD not found. Install from https://openscad.org") +``` + +## File Organization Guidelines + +### When to Create New Template vs Modify Existing + +**Create New Template** if: +- Fundamentally different design (e.g., adding holes, changing shape) +- Different use case (nameplates vs zipper pulls) +- Need to maintain both versions simultaneously + +**Modify Existing Template** if: +- Adjusting dimensions only +- Tweaking existing features +- Single-use or temporary change + +### Output Directory Naming +- Use descriptive names: `output_stl/`, `zipper-pulls/` +- Keep separate output directories for different variants +- Never mix different variants in the same directory (prevents confusion) + +### Generator Script Naming +- Match template name: `generate_nameplates.py` uses `nameplate_template.scad` +- Be explicit: `generate_zipper_pulls.py` not `generate_zippers.py` +- One template per generator for clarity + +## 3D Printing Notes + +### Recommended Slicer Settings +- **Material**: Two colors (white + blue PLA recommended) +- **Layer Height**: 0.1-0.2mm for clean text details +- **Infill**: 20-30% (thin parts don't need much) +- **Supports**: Not needed (flat design, print as modeled) +- **Orientation**: Print flat on bed +- **Bed Adhesion**: Brim or raft recommended for thin parts +- **First Layer**: Critical for thin 1.5mm base - ensure good adhesion + +### Multi-Color Printing +Two approaches: +1. **Filament swap**: Print white base, pause, swap to blue, continue +2. **Two prints**: Print white base separately, print blue top, glue together + +**Important**: Text must be fully engraved through blue layer to show white underneath. + +#### M600 Filament Change Height +When using M600 command for automatic filament change in slicer: +- **Base layer thickness**: 1.5mm +- **Actual M600 height**: 1.5mm + one layer height +- **Reason**: M600 triggers BEFORE the specified layer is printed. If set to exactly 1.5mm, the slicer will print one solid white layer at 1.5mm, then trigger M600, then start the blue outline and text. +- **Example**: With 0.2mm layer height, set M600 at 1.7mm (1.5 + 0.2) to ensure filament change happens at the correct layer. +- **Verification**: After slicing, verify that the layer immediately after M600 begins the blue top layer, not another white layer. + +## Project-Specific Quirks + +### Why Two Separate Templates for Nameplates/Zipper Pulls? +- Different dimensions (38mm vs 34mm height) +- Zipper pulls need hole, nameplates don't +- Maintains backward compatibility with existing nameplate output +- Allows independent evolution of each variant + +### Why Hardcoded OpenSCAD Path? +- Windows standard installation location is consistent +- Avoids PATH environment complexity +- Easy to update in one place per script +- Future: Consider environment variable or config file + +### Why Manual Font Installation? +- OpenSCAD uses system font registry, not file paths +- Ensures consistent font rendering across machines +- Font must be available to OpenSCAD process +- No programmatic workaround exists + +### Keycap Stem Preservation Algorithm +- Circular region around stem preserved at 100% scale +- Rest of keycap scaled down for better fit +- Cherry MX stem is exactly 4mm ร— 4mm by spec (must not scale) +- Circular preservation accommodates off-brand switch tolerances +- See `Key caps/CLAUDE.md` for detailed algorithm explanation diff --git a/Printing/Key caps/.claude/commands b/Printing/Key caps/.claude/commands new file mode 100644 index 0000000..afb10e4 --- /dev/null +++ b/Printing/Key caps/.claude/commands @@ -0,0 +1 @@ +C:/Users/Fred/claude-shared/commands \ No newline at end of file diff --git a/Printing/Key caps/.claude/index.html b/Printing/Key caps/.claude/index.html new file mode 100644 index 0000000..45d1cf1 --- /dev/null +++ b/Printing/Key caps/.claude/index.html @@ -0,0 +1,16 @@ + + + + +Directory listing for /Printing/Key caps/.claude/ + + +

Directory listing for /Printing/Key caps/.claude/

+
+ +
+ + diff --git a/Printing/Key caps/.claude/settings.local.json b/Printing/Key caps/.claude/settings.local.json new file mode 100644 index 0000000..425888d --- /dev/null +++ b/Printing/Key caps/.claude/settings.local.json @@ -0,0 +1,23 @@ +{ + "permissions": { + "allow": [ + "Bash(where:*)", + "Bash(if exist \"C:\\Program Files\\OpenSCAD\\openscad.exe\" echo Found at C:Program FilesOpenSCADopenscad.exe)", + "Bash(\"/c/Program Files/OpenSCAD/openscad.exe\" -o \"Body5_scaled_98percent.stl\" \"Body5_scaled.scad\")", + "Bash(\"/c/Program Files/OpenSCAD/openscad.exe\" --clear-cache -o \"Body5_scaled_98percent.stl\" \"Body5_scaled.scad\")", + "Bash(\"/c/Program Files/OpenSCAD/openscad.exe\" -o \"Body5_TEST_circle.stl\" \"Body5_test_circle.scad\")", + "Bash(\"/c/Program Files/OpenSCAD/openscad.exe\" -o \"test_cylinder.stl\" \"test_cylinder.scad\")", + "Bash(\"/c/Program Files/OpenSCAD/openscad.exe\" -o \"Body5_scaled_FIXED_98percent.stl\" \"Body5_scaled_FIXED.scad\")", + "Bash(\"/c/Program Files/OpenSCAD/openscad.exe\" --clear-cache -o \"Body5(1)_scaled_98percent.stl\" \"Body5(1)_scaled.scad\")", + "Bash(\"/c/Program Files/OpenSCAD/openscad.exe\" -o \"Body5(1)_scaled_98percent.stl\" \"Body5(1)_scaled.scad\")", + "Bash(ls:*)", + "Bash(\"/c/Program Files/OpenSCAD/openscad.exe\" -D number_to_generate=1 -o \"Body5_num1.stl\" \"generate_numbered_keycaps.scad\")", + "Bash(cmd /c generate_all_numbered_keycaps.bat)", + "Bash(for i in {2..20})", + "Bash(do \"/c/Program Files/OpenSCAD/openscad.exe\" -D number_to_generate=$i -o \"Body5_num$i.stl\" \"generate_numbered_keycaps.scad\")", + "Bash(done)" + ], + "deny": [], + "ask": [] + } +} diff --git a/Printing/Key caps/Body5(1)_scaled.scad b/Printing/Key caps/Body5(1)_scaled.scad new file mode 100644 index 0000000..b98f79e --- /dev/null +++ b/Printing/Key caps/Body5(1)_scaled.scad @@ -0,0 +1,55 @@ +// ===================================================== +// Keycap Body Scaling Script - FIXED VERSION +// ===================================================== +// This script scales down the keycap body while preserving +// the Cherry MX stem at its original 4mm x 4mm dimensions +// +// FIX: Centers the model first so cylinder operations work correctly +// ===================================================== + +// ----- ADJUSTABLE PARAMETERS ----- + +body_scale_xy = 0.98; // 98% of original (2% smaller) +body_scale_z = 1.00; // Keep height at 100% + +stem_diameter = 4.8; // Diameter of circular stem preservation region (4.8mm to preserve 4mm x 4mm cross) +stem_height = 10.0; // Height of stem region to preserve (tall enough to capture entire stem) +stem_z_offset = -1.0; // Z offset to position cylinder on the stem +$fn = 128; // Circle smoothness + +stl_filename = "Body5(1).stl"; + +// ===================================================== +// MAIN MODEL - CENTERED APPROACH +// ===================================================== + +// Center the imported model at origin +// The stem cross should be centered at X=209, Y=223.5 in the original STL +module centered_import() { + translate([-209, -223.5, 0]) // Center X and Y on the stem + import(stl_filename); +} + +union() { + // Part 1: Scaled body WITHOUT the circular stem region + difference() { + scale([body_scale_xy, body_scale_xy, body_scale_z]) { + centered_import(); + } + + // Cut out a cylinder where the stem is + translate([0, 0, stem_z_offset]) { + cylinder(h = stem_height, d = stem_diameter + 0.5, center = true); + } + } + + // Part 2: Original UNSCALED stem region (circular) + intersection() { + centered_import(); // NOT scaled - preserves original 4mm x 4mm cross + + // Cylinder defines the 4.8mm circular stem region to keep at 100% scale + translate([0, 0, stem_z_offset]) { + cylinder(h = stem_height, d = stem_diameter, center = true); + } + } +} diff --git a/Printing/Key caps/Body5_scaled.scad b/Printing/Key caps/Body5_scaled.scad new file mode 100644 index 0000000..cf2f820 --- /dev/null +++ b/Printing/Key caps/Body5_scaled.scad @@ -0,0 +1,98 @@ +// ===================================================== +// Keycap Body Scaling Script +// ===================================================== +// This script scales down the keycap body while preserving +// the Cherry MX stem at its original 4mm x 4mm dimensions +// within a CIRCULAR region (for off-brand switches) +// +// Usage: +// 1. Adjust body_scale_xy below (start with 0.98 = 98%) +// 2. Adjust stem_x and stem_y to position circle on the cross +// 3. Press F5 to preview, F6 to render +// 4. Export STL: File > Export > Export as STL +// ===================================================== + +// ----- ADJUSTABLE PARAMETERS ----- + +// Body scaling factor for X and Y axes +body_scale_xy = 0.98; // 98% of original (2% smaller) +body_scale_z = 1.00; // Keep height at 100% + +// ----- STEM DIMENSIONS ----- +// Position the circular preservation region on the stem cross + +stem_x = 209; // X position of stem center (adjust to align with cross) +stem_y = 223.5; // Y position of stem center (adjust to align with cross) +stem_z = 3.85; // Z position of stem center (adjust if needed) + +stem_diameter = 5.5; // Diameter of circular region around the stem +stem_height = 8.0; // Height of stem region to preserve + +$fn = 128; // Circle smoothness (128 = very smooth, 64 = balanced) + +stl_filename = "Body5.stl"; + +// ===================================================== +// MAIN MODEL +// ===================================================== + +union() { + // Part 1: Scaled body WITHOUT the circular stem region + difference() { + // The scaled keycap body (scaled around origin, then positioned) + translate([stem_x * (1 - body_scale_xy), stem_y * (1 - body_scale_xy), 0]) { + scale([body_scale_xy, body_scale_xy, body_scale_z]) { + import(stl_filename); + } + } + + // Cut out a cylinder where the stem is + translate([stem_x, stem_y, stem_z]) { + cylinder( + h = stem_height, + d = stem_diameter + 0.5, + center = true + ); + } + } + + // Part 2: Original stem at 100% scale in CIRCULAR region + intersection() { + // The original unscaled model + import(stl_filename); + + // A cylinder that defines the CIRCULAR stem region to keep + translate([stem_x, stem_y, stem_z]) { + cylinder( + h = stem_height, + d = stem_diameter, + center = true + ); + } + } +} + +// ===================================================== +// NOTES +// ===================================================== +// +// ADJUSTING THE CIRCULAR REGION POSITION: +// If the circle is not centered on the cross stem: +// 1. Open in OpenSCAD and press F5 to preview +// 2. Rotate the view to see the stem from below +// 3. Adjust stem_x and stem_y values until circle is centered on cross +// 4. Typical adjustment: try values between 208-210 for X, 222-225 for Y +// +// ITERATIVE TESTING WORKFLOW: +// 1. Start with body_scale_xy = 0.98 (98%) +// 2. Export STL and test print +// 3. If keycap is still too tight, decrease to 0.97 +// 4. If keycap is too loose, increase to 0.99 +// 5. Fine-tune in 0.005 increments (e.g., 0.975, 0.985) +// +// STEM DIAMETER ADJUSTMENT: +// - Increase stem_diameter if you need more clearance around the cross +// - Decrease stem_diameter for tighter tolerance +// - Typical range: 4.5mm to 6.0mm +// +// ===================================================== diff --git a/Printing/Key caps/Body5_scaled_FIXED.scad b/Printing/Key caps/Body5_scaled_FIXED.scad new file mode 100644 index 0000000..7299c4e --- /dev/null +++ b/Printing/Key caps/Body5_scaled_FIXED.scad @@ -0,0 +1,53 @@ +// ===================================================== +// Keycap Body Scaling Script - FIXED VERSION +// ===================================================== +// This script scales down the keycap body while preserving +// the Cherry MX stem at its original 4mm x 4mm dimensions +// +// FIX: Centers the model first so cylinder operations work correctly +// ===================================================== + +// ----- ADJUSTABLE PARAMETERS ----- + +body_scale_xy = 0.98; // 98% of original (2% smaller) +body_scale_z = 1.00; // Keep height at 100% + +stem_diameter = 5.5; // Diameter of circular stem preservation region +stem_height = 8.0; // Height of stem region to preserve +$fn = 128; // Circle smoothness + +stl_filename = "Body5.stl"; + +// ===================================================== +// MAIN MODEL - CENTERED APPROACH +// ===================================================== + +// First, we need to center the imported model +// The original STL is positioned around xโ‰ˆ209, yโ‰ˆ220 +// We'll measure the bounding box and center it + +module centered_import() { + // Import and center the model at origin + translate([-209, -223.5, -4.9]) // Approximate center based on STL coordinates + import(stl_filename); +} + +union() { + // Part 1: Scaled body WITHOUT the circular stem region + difference() { + scale([body_scale_xy, body_scale_xy, body_scale_z]) { + centered_import(); + } + + // Cut out a cylinder where the stem is (now centered at origin) + cylinder(h = stem_height, d = stem_diameter + 0.5, center = true); + } + + // Part 2: Original unscaled stem region (circular) + intersection() { + centered_import(); + + // Cylinder defines the circular stem region to keep + cylinder(h = stem_height, d = stem_diameter, center = true); + } +} diff --git a/Printing/Key caps/Body5_test_circle.scad b/Printing/Key caps/Body5_test_circle.scad new file mode 100644 index 0000000..3182d7e --- /dev/null +++ b/Printing/Key caps/Body5_test_circle.scad @@ -0,0 +1,31 @@ +// TEST VERSION - Exaggerated circular cutout to verify it's working + +body_scale_xy = 0.98; +body_scale_z = 1.00; + +stem_diameter = 8.0; // MUCH larger to make the circle obvious +stem_height = 10.0; // Taller to ensure we capture everything +stem_z_position = 0; +$fn = 128; + +stl_filename = "Body5.stl"; + +union() { + difference() { + scale([body_scale_xy, body_scale_xy, body_scale_z]) { + import(stl_filename); + } + // Large obvious cylinder + translate([0, 0, stem_z_position]) { + cylinder(h = 20, d = stem_diameter + 0.5, center = true); + } + } + + intersection() { + import(stl_filename); + // Large obvious cylinder + translate([0, 0, stem_z_position]) { + cylinder(h = 20, d = stem_diameter, center = true); + } + } +} diff --git a/Printing/Key caps/CLAUDE.md b/Printing/Key caps/CLAUDE.md new file mode 100644 index 0000000..0f8771e --- /dev/null +++ b/Printing/Key caps/CLAUDE.md @@ -0,0 +1,80 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is an OpenSCAD project for scaling 3D-printed mechanical keyboard keycaps while preserving the Cherry MX stem dimensions. The project contains scripts to reduce keycap body dimensions for better socket fit while maintaining the exact 4mm ร— 4mm stem cross dimensions required for Cherry MX switches. + +## Key Commands + +### Rendering STL Files + +On Windows, OpenSCAD is typically installed at `C:\Program Files\OpenSCAD\openscad.exe`. Use Git Bash path format: + +```bash +# Render a SCAD file to STL +"/c/Program Files/OpenSCAD/openscad.exe" -o "output.stl" "input.scad" + +# Clear cache and render (recommended when changing imports) +"/c/Program Files/OpenSCAD/openscad.exe" --clear-cache -o "output.stl" "input.scad" +``` + +### Verify OpenSCAD Installation + +```bash +# Check if OpenSCAD is installed +if exist "C:\Program Files\OpenSCAD\openscad.exe" echo Found at C:\Program Files\OpenSCAD\openscad.exe +``` + +## Project Architecture + +### Main Scripts + +- **`Body5_scaled_FIXED.scad`**: The primary working script that uses a centered-import approach. This is the recommended version for scaling keycaps. +- **`Body5_scaled.scad`**: Earlier version that manually positions the stem preservation cylinder using explicit X/Y coordinates. +- **`Body5_test_circle.scad`**: Test script with exaggerated parameters (larger cylinder) to verify the circular stem preservation is working. +- **`test_cylinder.scad`**: Simple cylinder rendering test for OpenSCAD verification. + +### STL Files + +- **`Body5.stl`**: The original keycap model that gets imported and processed by the SCAD scripts. +- **`Body2.stl`**: Alternative keycap model. +- **Generated STL files**: Output from rendering the SCAD scripts (e.g., `Body5_scaled_98percent.stl`). + +### Scaling Algorithm + +The scripts use a two-part union approach: + +1. **Scaled Body with Cutout**: The entire keycap is scaled down by `body_scale_xy`, then a cylinder is subtracted where the stem should be +2. **Unscaled Stem Region**: A cylinder intersection preserves the original stem region at 100% scale +3. **Union**: Both parts are merged, creating a keycap with scaled body but original stem dimensions + +The `_FIXED` version centers the model at the origin before operations, making cylinder placement simpler. The non-FIXED version requires manual positioning of the preservation cylinder using `stem_x` and `stem_y` coordinates. + +### Critical Parameters + +- **`body_scale_xy`**: X/Y scaling factor (typically 0.97-0.99 for 1-3% reduction) +- **`body_scale_z`**: Z scaling factor (keep at 1.00 to maintain keycap height) +- **`stem_diameter`**: Diameter of circular preservation region (default: 5.5mm) +- **`stem_height`**: Height of stem region (default: 8.0mm) +- **`$fn`**: OpenSCAD circle resolution (64-128, higher = smoother but slower) + +### Centering Approach (FIXED version) + +The `centered_import()` module translates the imported STL by `[-209, -223.5, -4.9]` to center it at the origin. These values were determined by analyzing the original STL's bounding box. This allows stem operations to use simple `cylinder()` calls at the origin rather than calculating offsets. + +## Development Workflow + +1. Modify SCAD script parameters (typically just `body_scale_xy`) +2. Render to STL using OpenSCAD command line or GUI (F5 preview, F6 full render in GUI) +3. Import STL into slicer and print test keycap +4. Measure fit and iterate on scaling factor +5. Typical iteration: start at 0.98, adjust by 0.01 increments based on fit testing + +## Important Notes + +- The Cherry MX stem cross is exactly 4mm ร— 4mm by specification and must not be scaled +- The circular preservation region accommodates off-brand switches that may have slightly different tolerances +- All SCAD scripts expect `Body5.stl` (or specified STL file) to be in the same directory +- Rendering is computationally intensive due to importing the STL twice (once for scaling, once for stem preservation) diff --git a/Printing/Key caps/README_scaling.md b/Printing/Key caps/README_scaling.md new file mode 100644 index 0000000..0fd7f24 --- /dev/null +++ b/Printing/Key caps/README_scaling.md @@ -0,0 +1,109 @@ +# Keycap Scaling Guide + +## Quick Start + +1. **Open the script**: Open `Body5_scaled.scad` in OpenSCAD +2. **Adjust scaling**: Edit the `body_scale_xy` parameter at the top (default: 0.98 = 98%) +3. **Preview**: Press `F5` to see a quick preview +4. **Render**: Press `F6` to fully render (required before export) +5. **Export**: Go to `File > Export > Export as STL` +6. **Test print**: Print and check the fit +7. **Iterate**: Adjust the scale parameter and repeat if needed + +## Key Parameters + +### `body_scale_xy` +- **Default**: `0.98` (98% of original size) +- **Purpose**: Scales the X and Y dimensions of the keycap body +- **Recommendations**: + - Start with `0.98` for a 2% reduction + - If too tight: try `0.97` (3% reduction) + - If too loose: try `0.99` (1% reduction) + - Fine-tune: use `0.975`, `0.985`, etc. + +### `body_scale_z` +- **Default**: `1.00` (no scaling) +- **Purpose**: Scales the Z (height) dimension +- **Recommendation**: Keep at `1.00` to maintain keycap height + +### Stem Parameters (Advanced) +Only adjust these if the stem preservation isn't working correctly: + +- `stem_diameter`: Diameter of the circular preservation region (default: 5.5mm) +- `stem_height`: Height of the stem (default: 4.0mm) +- `stem_z_position`: Vertical position offset (default: -2.0mm) +- `$fn`: Circle smoothness (default: 64, range: 32-128) + +## How It Works + +The script uses a two-part approach: + +1. **Scaled Body**: The entire keycap is scaled down by your chosen percentage, BUT a circular region around the stem is cut out +2. **Original Stem**: The circular stem region is preserved at 100% scale (keeping the 4mm ร— 4mm Cherry MX cross dimensions) +3. **Combined**: Both parts are merged together + +This ensures: +- โœ… The outer keycap body fits better in sockets (scaled down) +- โœ… The Cherry MX stem remains exactly 4mm ร— 4mm (unscaled) +- โœ… The circular region around the stem is preserved (for off-brand switches) +- โœ… The stem stays centered and properly positioned + +## Workflow for Iterative Testing + +1. Export STL with `body_scale_xy = 0.98` +2. Slice and print the keycap +3. Test fit on your keyboard +4. **If too tight**: Decrease scale (try 0.97) +5. **If too loose**: Increase scale (try 0.99) +6. **If just right**: You're done! + +## Troubleshooting + +### The stem looks wrong or is scaled +- The circular stem region might not be positioned correctly +- Measure your stem in the original STL +- Adjust `stem_z_position`, `stem_diameter`, or `stem_height` +- If the circle is too jagged, increase `$fn` to 128 + +### The keycap won't render +- Make sure `Body5.stl` is in the same folder as the `.scad` file +- Check for errors in the OpenSCAD console (bottom of window) + +### Rendering is slow +- This is normal! The script imports the STL twice +- `F5` preview is fast but lower quality +- `F6` render is slow but required for STL export +- Consider reducing the preview quality in OpenSCAD preferences + +### The seam between body and stem is visible +- This is usually not noticeable in the printed part +- If needed, increase `stem_diameter` by 0.5mm increments (try 6.0mm or 6.5mm) + +## File Structure + +``` +Key caps/ +โ”œโ”€โ”€ Body5.stl # Original keycap STL +โ”œโ”€โ”€ Body5_scaled.scad # OpenSCAD scaling script +โ””โ”€โ”€ README_scaling.md # This guide +``` + +## Export Settings + +When exporting STL from OpenSCAD: +- File format: STL (binary is smaller, ASCII is more compatible) +- Units: millimeters (mm) + +## Next Steps + +After exporting your scaled STL: +1. Import into your slicer (Cura, PrusaSlicer, etc.) +2. Use typical keycap print settings: + - Layer height: 0.1-0.2mm + - Infill: 15-20% + - Supports: Probably not needed for most keycaps +3. Print and test! + +--- + +**Note**: The Cherry MX stem dimensions (4mm ร— 4mm) are standardized. The script is designed to preserve these exactly, so you should only need to adjust the `body_scale_xy` parameter for tolerance testing. diff --git a/Printing/Key caps/generate_all_numbered_keycaps.bat b/Printing/Key caps/generate_all_numbered_keycaps.bat new file mode 100644 index 0000000..394b390 --- /dev/null +++ b/Printing/Key caps/generate_all_numbered_keycaps.bat @@ -0,0 +1,34 @@ +@echo off +REM ===================================================== +REM Batch Script to Generate All 20 Numbered Keycaps +REM ===================================================== +REM This script uses OpenSCAD command line to generate +REM all 20 numbered keycap STL files automatically +REM ===================================================== + +echo Starting generation of 20 numbered keycap STL files... +echo. + +set OPENSCAD="C:\Program Files\OpenSCAD\openscad.exe" +set SCRIPT=generate_numbered_keycaps.scad + +REM Loop through numbers 1-20 +for /L %%i in (1,1,20) do ( + echo Generating Body5_num%%i.stl... + %OPENSCAD% -D number_to_generate=%%i -o Body5_num%%i.stl %SCRIPT% + if errorlevel 1 ( + echo ERROR: Failed to generate Body5_num%%i.stl + pause + exit /b 1 + ) + echo Completed Body5_num%%i.stl + echo. +) + +echo. +echo ===================================================== +echo All 20 numbered keycap STL files generated successfully! +echo Files created: Body5_num1.stl through Body5_num20.stl +echo ===================================================== +echo. +pause diff --git a/Printing/Key caps/generate_numbered_keycaps.scad b/Printing/Key caps/generate_numbered_keycaps.scad new file mode 100644 index 0000000..6a28b68 --- /dev/null +++ b/Printing/Key caps/generate_numbered_keycaps.scad @@ -0,0 +1,102 @@ +// ===================================================== +// Numbered Keycap Generator - Multi-Color Printing +// ===================================================== +// This script generates 20 numbered keycap STL files +// with recessed numbers for multi-material printing +// +// Numbers are 14mm tall, recessed 0.6mm deep +// Positioned on triangular top face, centered on apex-to-midpoint line +// Apex oriented as "up" (aligned with Cherry MX cross arm) +// ===================================================== + +// ----- ADJUSTABLE PARAMETERS ----- + +number_to_generate = 1; // Change this value (1-20) to generate different numbers + +number_height = 14; // Height of numbers in mm +number_depth = 0.6; // Recess depth for color saturation +text_font = "Liberation Sans:style=Bold"; // Font (use bold for visibility) +text_thickness = number_depth; // Same as recess depth + +// Triangular face positioning +// The top face is opposite the stem pocket +// One apex aligns with a Cherry MX cross arm +face_z_position = 8.0; // Approximate Z height of top triangular face +face_rotation = 0; // Rotation to align apex with cross arm +text_y_offset = 0; // Offset along apex-to-midpoint centerline + +$fn = 128; // Circle smoothness + +base_stl_filename = "Body5(1)_scaled_98percent.stl"; + +// ===================================================== +// CENTERED IMPORT MODULE +// ===================================================== + +module centered_base() { + // Import the scaled keycap base + // Already centered from the previous scaling script + import(base_stl_filename); +} + +// ===================================================== +// NUMBERED TEXT MODULE +// ===================================================== + +module recessed_number(num) { + // Create text for the number + rotate([0, 0, face_rotation]) { + translate([0, text_y_offset, face_z_position]) { + rotate([0, 0, 0]) { // Text faces up + linear_extrude(height = text_thickness + 1) { // +1 to ensure clean cut + text( + str(num), + size = number_height, + halign = "center", + valign = "center", + font = text_font + ); + } + } + } + } +} + +// ===================================================== +// MAIN MODEL +// ===================================================== + +difference() { + // The base keycap + centered_base(); + + // Subtract the recessed number + recessed_number(number_to_generate); +} + +// ===================================================== +// USAGE INSTRUCTIONS +// ===================================================== +// +// MANUAL GENERATION (one at a time): +// 1. Change number_to_generate value (1-20) +// 2. Render with F6 +// 3. Export as STL with desired filename +// +// COMMAND LINE GENERATION (all 20 files): +// Use the companion batch script or run: +// openscad -D number_to_generate=N -o Body5_numN.stl generate_numbered_keycaps.scad +// +// ADJUSTING POSITION: +// - face_z_position: Move numbers up/down on the keycap +// - face_rotation: Rotate around Z axis to align with cross arm +// - text_y_offset: Move along the apex-to-midpoint centerline +// +// MULTI-MATERIAL PRINTING: +// 1. Slice the numbered STL normally +// 2. At layer with recess (approx 0.6mm from top), insert material change +// 3. Printer will pause, swap filament for number color +// 4. Resume printing - number will be filled with new color +// 5. At end of recess depth, change back to original material +// +// ===================================================== diff --git a/Printing/Key caps/index.html b/Printing/Key caps/index.html new file mode 100644 index 0000000..43b120e --- /dev/null +++ b/Printing/Key caps/index.html @@ -0,0 +1,24 @@ + + + + +Directory listing for /Printing/Key caps/ + + +

Directory listing for /Printing/Key caps/

+
+ +
+ + diff --git a/Printing/Key caps/test_cylinder.scad b/Printing/Key caps/test_cylinder.scad new file mode 100644 index 0000000..590c8b3 --- /dev/null +++ b/Printing/Key caps/test_cylinder.scad @@ -0,0 +1,3 @@ +// Simple cylinder test +$fn = 128; +cylinder(h = 10, d = 5.0, center = true); diff --git a/Printing/Nameplates/.claude/commands b/Printing/Nameplates/.claude/commands new file mode 100644 index 0000000..afb10e4 --- /dev/null +++ b/Printing/Nameplates/.claude/commands @@ -0,0 +1 @@ +C:/Users/Fred/claude-shared/commands \ No newline at end of file diff --git a/Printing/Nameplates/.claude/index.html b/Printing/Nameplates/.claude/index.html new file mode 100644 index 0000000..873d001 --- /dev/null +++ b/Printing/Nameplates/.claude/index.html @@ -0,0 +1,16 @@ + + + + +Directory listing for /Printing/Nameplates/.claude/ + + +

Directory listing for /Printing/Nameplates/.claude/

+
+ +
+ + diff --git a/Printing/Nameplates/.claude/settings.local.json b/Printing/Nameplates/.claude/settings.local.json new file mode 100644 index 0000000..e333c3a --- /dev/null +++ b/Printing/Nameplates/.claude/settings.local.json @@ -0,0 +1,36 @@ +{ + "permissions": { + "allow": [ + "Bash(python:*)", + "Bash(dir:*)", + "Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D \"name=\"\"Test\"\"\" -o test_output.stl nameplate_template.scad)", + "Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -o checkmark_test.stl checkmark_template.scad)", + "Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D \"name=\"\"Test\"\"\" -o test_hole_position.stl zipper_pull_template.scad)", + "Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D \"name=\"\"Christopher\"\"\" -o test_christopher.stl zipper_pull_template.scad)", + "Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D \"name=\"\"Christopher\"\"\" -o test_christopher_13pt.stl zipper_pull_template.scad)", + "Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D \"name=\"\"Fred\"\"\" -o test_fred_bold.stl zipper_pull_template.scad)", + "Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D \"name=\"\"Zoe\"\"\" -o test_zoe.stl zipper_pull_template.scad)", + "Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D \"name=\"\"Fred\"\"\" -o test_fred_dynamic.stl zipper_pull_template.scad)", + "Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D \"name=\"\"Christopher\"\"\" -o test_christopher_dynamic.stl zipper_pull_template.scad)", + "Bash(timeout:*)", + "Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D \"name=\"\"Zoe\"\"\" -o test_zoe_raised.stl zipper_pull_raised_text_template.scad)", + "Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D \"name=\"\"Fred\"\"\" -o test_fred_raised.stl zipper_pull_raised_text_template.scad)", + "Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D \"name=\"\"Christopher\"\"\" -o test_christopher_raised.stl zipper_pull_raised_text_template.scad)", + "Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D \"name=\"\"Fred\"\"\" -D \"base_text_size=13\" -o test_fred_debug.stl zipper_pull_raised_text_template.scad)", + "Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -o test_simple_fred.stl test_simple_fred.scad)", + "Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D \"name=\"\"Fred\"\"\" -o test_fred_reduced_bold.stl zipper_pull_raised_text_template_REDUCED_BOLD.scad)", + "Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D \"name=\"\"Bennet\"\"\" -o test_bennet_facedown.stl zipper_pull_raised_text_template.scad)", + "Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D \"name=\"\"Bennett\"\"\" -o test_bennett_facedown.stl zipper_pull_raised_text_template.scad)", + "Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -D \"name=\"\"Bennett\"\"\" -o test_bennett_facedown.3mf zipper_pull_raised_text_template.scad)", + "Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -o test_font.stl test_font.scad)", + "Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -o test_debug.3mf test_debug.scad)", + "Bash(\"C:\\Program Files\\OpenSCAD\\openscad.exe\" -o test_blue_only.3mf test_blue_only.scad)" + ], + "deny": [], + "ask": [] + }, + "enableAllProjectMcpServers": true, + "enabledMcpjsonServers": [ + "n8n-mcp" + ] +} diff --git a/Printing/Nameplates/.github/copilot-instructions.md b/Printing/Nameplates/.github/copilot-instructions.md new file mode 100644 index 0000000..0fe9015 --- /dev/null +++ b/Printing/Nameplates/.github/copilot-instructions.md @@ -0,0 +1,278 @@ +# AI Coding Instructions for Nameplates Generator + +## Project Overview + +A batch 3D nameplate generator using **Python + OpenSCAD**. Reads names from text file, generates parametric two-layer oval nameplates with engraved text, outputs STL files for 3D printing. + +**Design**: White base (1.5mm) + blue top layer (1mm) with text engraved through blue layer to expose white underneath. + +## Critical Dependencies + +### OpenSCAD Installation +- **Path**: `C:\Program Files\OpenSCAD\openscad.exe` (hardcoded in line 36 of `generate_nameplates.py`) +- Download: https://openscad.org/downloads.html +- **Must update path** if installed elsewhere + +### Font Requirements (CRITICAL) +- **Font**: Fordscript cursive font (`Fordscript.ttf` in project root) +- **Installation**: Must be installed in Windows (right-click โ†’ Install for all users) +- **Why**: OpenSCAD references fonts by name, not file path +- **Reference in SCAD**: `font="Fordscript"` (line 44 of `nameplate_template.scad`) +- **Path hardcoded**: `font_file = "C:/Users/Fred/claude/Fordscript.ttf"` in SCAD (line 10) - informational only, not used by OpenSCAD + +## Architecture & Data Flow + +``` +names.txt โ†’ generate_nameplates.py โ†’ OpenSCAD CLI โ†’ output_stl/*.stl + โ†‘ + nameplate_template.scad +``` + +### Pipeline +1. **Input**: `names.txt` (one name per line, UTF-8 encoded) +2. **Processing**: Python script iterates, calling OpenSCAD for each name +3. **OpenSCAD**: Renders 3D model from parametric template +4. **Output**: Individual STL files in `output_stl/` directory + +### Key Files +- `generate_nameplates.py` - Batch processor, subprocess orchestration +- `nameplate_template.scad` - Parametric OpenSCAD template +- `names.txt` - Input data (plain text, one name per line) +- `output_stl/` - Generated STL files (created automatically) +- `Fordscript.ttf` - Custom font (must be installed) + +## Critical Developer Workflows + +### Generate All Nameplates +```bash +python generate_nameplates.py +``` + +### Test Single Nameplate (Manual) +```powershell +# PowerShell syntax +& "C:\Program Files\OpenSCAD\openscad.exe" -D 'name="TestName"' -o test_output.stl nameplate_template.scad + +# Or from CMD +"C:\Program Files\OpenSCAD\openscad.exe" -D "name=\"TestName\"" -o test_output.stl nameplate_template.scad +``` + +### Preview in OpenSCAD GUI +```powershell +# Open template in GUI for visual editing +& "C:\Program Files\OpenSCAD\openscad.exe" nameplate_template.scad +``` + +Then manually edit `name` parameter at top of file to preview different text. + +### Validate Font Installation +```powershell +# Check if font is installed +Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts" | Select-String "Fordscript" + +# Or check fonts directory +ls $env:WINDIR\Fonts\Fordscript* +``` + +## Project-Specific Conventions + +### Name Sanitization +Python script converts names to safe filenames: +- Removes special characters (keeps alphanumeric, spaces, hyphens, underscores) +- Replaces spaces with underscores +- Example: `"John Doe"` โ†’ `John_Doe.stl` + +### OpenSCAD Command Pattern +```python +cmd = [ + r'C:\Program Files\OpenSCAD\openscad.exe', + '-D', f'name="{escaped_name}"', # Set variable + '-o', output_file, # Output file + template_file # Input SCAD file +] +``` + +**Key flags**: +- `-D name="value"` - Override parameter in SCAD file +- `-o file.stl` - Output file path +- No flag for input file (positional argument) + +### SCAD Template Structure +```scad +// Parameters (overridable via -D flag) +name = "NAME"; +oval_width = 100; +oval_height = 38; +// ... more parameters + +// Modules (functions) +module oval(width, height, depth) { ... } +module base_layer() { ... } +module top_layer() { ... } +module nameplate() { ... } + +// Execution (builds the model) +nameplate(); +``` + +## Common Pitfalls + +### Font Not Found Error +**Symptom**: OpenSCAD renders but text is missing or uses default font. + +**Causes**: +1. Font not installed in Windows (right-click TTF โ†’ Install) +2. Font name mismatch (must be exactly "Fordscript" in SCAD) +3. OpenSCAD needs restart after font installation + +**Fix**: Install font, restart OpenSCAD, verify with manual test. + +### Special Characters in Names +**Symptom**: Subprocess error or malformed STL. + +**Cause**: Shell escaping issues with quotes, backslashes, etc. + +**Current handling**: `escaped_name = name.replace('"', '\\"')` (line 33) + +**Extension**: Add more sanitization if needed: +```python +# Escape more shell-sensitive characters +escaped_name = name.replace('"', '\\"').replace('\\', '\\\\') +``` + +### OpenSCAD Path Issues +**Symptom**: `FileNotFoundError: OpenSCAD not found`. + +**Fix**: Update hardcoded path in `generate_nameplates.py:36`: +```python +cmd = [ + r'C:\Program Files\OpenSCAD\openscad.exe', # โ† Update this + # ... +] +``` + +**Better approach**: Use environment variable or config file: +```python +OPENSCAD_PATH = os.getenv('OPENSCAD_PATH', r'C:\Program Files\OpenSCAD\openscad.exe') +``` + +## Modifying Dimensions + +All dimensions in `nameplate_template.scad` (lines 6-12): + +| Parameter | Current Value | Purpose | +|-----------|---------------|---------| +| `oval_width` | 100mm | Overall width | +| `oval_height` | 38mm | Overall height (proportional) | +| `base_thickness` | 1.5mm | White layer thickness | +| `top_thickness` | 1mm | Blue layer thickness | +| `base_offset` | 2mm | White extension beyond blue | +| `text_size` | 15mm | Font size | +| `text_depth` | 1mm | Engraving depth (full blue layer) | + +### Maintaining Proportions +Height typically 38% of width for aesthetic oval shape: +```scad +oval_height = oval_width * 0.38; +``` + +### Text Depth Critical +`text_depth` must equal `top_thickness` to engrave through entire blue layer: +```scad +text_depth = 1; // Must match top_thickness for full engraving +``` + +If `text_depth < top_thickness`, text won't reach white base. If `text_depth > top_thickness`, text cuts into white base (undesirable). + +## Extending the Template + +### Add New Shape Option +```scad +// Add rectangle module +module rectangle(width, height, depth) { + cube([width, height, depth], center=true); +} + +// Add shape parameter at top +shape = "oval"; // or "rectangle" + +// Use conditional in base_layer +module base_layer() { + if (shape == "rectangle") + rectangle(oval_width + base_offset*2, oval_height + base_offset*2, base_thickness); + else + oval(oval_width + base_offset*2, oval_height + base_offset*2, base_thickness); +} +``` + +### Add Logo/Icon +```scad +// Import SVG or PNG +module logo() { + translate([0, -oval_height/3, base_thickness + top_thickness - text_depth]) + linear_extrude(height=text_depth) + import("logo.svg", center=true); +} + +// Add to top_layer difference +difference() { + // ... existing blue oval + logo(); // Cut logo +} +``` + +### Batch Different Sizes +Modify Python script to pass multiple parameters: +```python +cmd = [ + r'C:\Program Files\OpenSCAD\openscad.exe', + '-D', f'name="{escaped_name}"', + '-D', f'oval_width={width}', # Variable width + '-D', f'text_size={text_size}', # Variable text size + '-o', output_file, + template_file +] +``` + +## Troubleshooting Commands + +```powershell +# Check OpenSCAD version +& "C:\Program Files\OpenSCAD\openscad.exe" --version + +# Test SCAD syntax (no output, just validation) +& "C:\Program Files\OpenSCAD\openscad.exe" --check nameplate_template.scad + +# Render with verbose output +& "C:\Program Files\OpenSCAD\openscad.exe" -D 'name="Test"' -o test.stl nameplate_template.scad --verbose + +# List available fonts in OpenSCAD (run in OpenSCAD console) +# Help โ†’ Font List +``` + +## 3D Printing Notes + +- **Material**: Two colors required (white + blue recommended) +- **Layer Height**: 0.1-0.2mm for clean text +- **Infill**: 20-30% sufficient for thin nameplates +- **Supports**: Not needed (flat design) +- **Orientation**: Print flat (as modeled) +- **Bed Adhesion**: Brim or raft recommended for thin parts + +## Quick Reference + +### Add New Name +1. Edit `names.txt`, add line with new name +2. Run `python generate_nameplates.py` +3. Find STL in `output_stl/` directory + +### Change All Dimensions +1. Edit parameters at top of `nameplate_template.scad` +2. Run `python generate_nameplates.py` to regenerate all +3. Or manually test with single name first + +### Install Font on New Machine +1. Copy `Fordscript.ttf` to new machine +2. Right-click โ†’ "Install for all users" +3. Restart OpenSCAD if running +4. Test with manual OpenSCAD command diff --git a/Printing/Nameplates/.github/index.html b/Printing/Nameplates/.github/index.html new file mode 100644 index 0000000..45dc26a --- /dev/null +++ b/Printing/Nameplates/.github/index.html @@ -0,0 +1,15 @@ + + + + +Directory listing for /Printing/Nameplates/.github/ + + +

Directory listing for /Printing/Nameplates/.github/

+
+ +
+ + diff --git a/Printing/Nameplates/300.103.pdf b/Printing/Nameplates/300.103.pdf new file mode 100644 index 0000000..ed52637 Binary files /dev/null and b/Printing/Nameplates/300.103.pdf differ diff --git a/Printing/Nameplates/CLAUDE.md b/Printing/Nameplates/CLAUDE.md new file mode 100644 index 0000000..2123da9 --- /dev/null +++ b/Printing/Nameplates/CLAUDE.md @@ -0,0 +1,68 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is a 3D nameplate generator that creates two-layer oval nameplates with engraved text using OpenSCAD. The system batch-processes names from a text file and outputs STL files ready for 3D printing. + +## Architecture + +**Pipeline:** +1. Python script (`generate_nameplates.py`) reads names from `names.txt` +2. For each name, it invokes OpenSCAD CLI with the parametric template +3. OpenSCAD renders the 3D model using `nameplate_template.scad` +4. STL files are output to `output_stl/` directory + +**Design Structure:** +- Two-layer design: white base (1.5mm) + blue top layer (1mm) +- Text is engraved completely through the blue layer to expose white underneath +- Oval shape created by scaling a cylinder +- All dimensions parametric for easy customization + +## Key Commands + +**Generate nameplates:** +```bash +python generate_nameplates.py +``` + +**Test single nameplate (manual):** +```bash +"C:\Program Files\OpenSCAD\openscad.exe" -D "name=\"TestName\"" -o output.stl nameplate_template.scad +``` + +## Critical Configuration + +**OpenSCAD Path:** +The script uses hardcoded path: `C:\Program Files\OpenSCAD\openscad.exe` +Update in `generate_nameplates.py:36` if OpenSCAD is installed elsewhere. + +**Font Requirements:** +- Uses Fordscript font (Fordscript.ttf in project root) +- Font MUST be installed in Windows (right-click โ†’ Install) for OpenSCAD to access it +- OpenSCAD references fonts by name, not file path +- Referenced in template as `font="Fordscript"` (line 44) + +**Dimensions:** +Default nameplate size: 3" tall ร— 8" wide (76mm ร— 200mm) +All dimensions in `nameplate_template.scad:6-12` + +## File Purposes + +- `nameplate_template.scad` - OpenSCAD parametric template defining the 3D geometry +- `generate_nameplates.py` - Batch processor that calls OpenSCAD for each name +- `names.txt` - Input file with one name per line +- `Fordscript.ttf` - Custom cursive font (must be installed in Windows) +- `output_stl/` - Generated STL files (one per name) + +## Modifying Dimensions + +Key parameters in `nameplate_template.scad`: +- `oval_height` - Overall height (currently 76mm = 3 inches) +- `oval_width` - Overall width (currently 200mm โ‰ˆ 8 inches) +- `base_thickness` - White layer thickness (1.5mm) +- `top_thickness` - Blue layer thickness (1mm) +- `text_depth` - Engraving depth (1mm - cuts through entire blue layer) +- `text_size` - Font size (30mm) +- `base_offset` - How much white extends beyond blue (2mm) diff --git a/Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 01.dxf b/Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 01.dxf new file mode 100644 index 0000000..0620a1b --- /dev/null +++ b/Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 01.dxf @@ -0,0 +1,19092 @@ + 0 +SECTION + 2 +HEADER + 9 +$ACADVER + 1 +AC1024 + 9 +$ACADMAINTVER + 70 + 6 + 9 +$DWGCODEPAGE + 3 +ANSI_1252 + 9 +$INSBASE + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$EXTMIN + 10 +1.000000000000000E+20 + 20 +1.000000000000000E+20 + 30 +1.000000000000000E+20 + 9 +$EXTMAX + 10 +-1.000000000000000E+20 + 20 +-1.000000000000000E+20 + 30 +-1.000000000000000E+20 + 9 +$LIMMIN + 10 +0.0 + 20 +0.0 + 9 +$LIMMAX + 10 +12.0 + 20 +9.0 + 9 +$ORTHOMODE + 70 + 0 + 9 +$REGENMODE + 70 + 1 + 9 +$FILLMODE + 70 + 1 + 9 +$QTEXTMODE + 70 + 0 + 9 +$MIRRTEXT + 70 + 1 + 9 +$LTSCALE + 40 +1.0 + 9 +$ATTMODE + 70 + 1 + 9 +$TEXTSIZE + 40 +0.2 + 9 +$TRACEWID + 40 +0.05 + 9 +$TEXTSTYLE + 7 +Standard + 9 +$CLAYER + 8 +0 + 9 +$CELTYPE + 6 +ByLayer + 9 +$CECOLOR + 62 + 256 + 9 +$CELTSCALE + 40 +1.0 + 9 +$DISPSILH + 70 + 0 + 9 +$DIMSCALE + 40 +1.0 + 9 +$DIMASZ + 40 +0.18 + 9 +$DIMEXO + 40 +0.0625 + 9 +$DIMDLI + 40 +0.38 + 9 +$DIMRND + 40 +0.0 + 9 +$DIMDLE + 40 +0.0 + 9 +$DIMEXE + 40 +0.18 + 9 +$DIMTP + 40 +0.0 + 9 +$DIMTM + 40 +0.0 + 9 +$DIMTXT + 40 +0.18 + 9 +$DIMCEN + 40 +0.09 + 9 +$DIMTSZ + 40 +0.0 + 9 +$DIMTOL + 70 + 0 + 9 +$DIMLIM + 70 + 0 + 9 +$DIMTIH + 70 + 1 + 9 +$DIMTOH + 70 + 1 + 9 +$DIMSE1 + 70 + 0 + 9 +$DIMSE2 + 70 + 0 + 9 +$DIMTAD + 70 + 0 + 9 +$DIMZIN + 70 + 0 + 9 +$DIMBLK + 1 + + 9 +$DIMASO + 70 + 1 + 9 +$DIMSHO + 70 + 1 + 9 +$DIMPOST + 1 + + 9 +$DIMAPOST + 1 + + 9 +$DIMALT + 70 + 0 + 9 +$DIMALTD + 70 + 2 + 9 +$DIMALTF + 40 +25.4 + 9 +$DIMLFAC + 40 +1.0 + 9 +$DIMTOFL + 70 + 0 + 9 +$DIMTVP + 40 +0.0 + 9 +$DIMTIX + 70 + 0 + 9 +$DIMSOXD + 70 + 0 + 9 +$DIMSAH + 70 + 0 + 9 +$DIMBLK1 + 1 + + 9 +$DIMBLK2 + 1 + + 9 +$DIMSTYLE + 2 +Standard + 9 +$DIMCLRD + 70 + 0 + 9 +$DIMCLRE + 70 + 0 + 9 +$DIMCLRT + 70 + 0 + 9 +$DIMTFAC + 40 +1.0 + 9 +$DIMGAP + 40 +0.09 + 9 +$DIMJUST + 70 + 0 + 9 +$DIMSD1 + 70 + 0 + 9 +$DIMSD2 + 70 + 0 + 9 +$DIMTOLJ + 70 + 1 + 9 +$DIMTZIN + 70 + 0 + 9 +$DIMALTZ + 70 + 0 + 9 +$DIMALTTZ + 70 + 0 + 9 +$DIMUPT + 70 + 0 + 9 +$DIMDEC + 70 + 4 + 9 +$DIMTDEC + 70 + 4 + 9 +$DIMALTU + 70 + 2 + 9 +$DIMALTTD + 70 + 2 + 9 +$DIMTXSTY + 7 +Standard + 9 +$DIMAUNIT + 70 + 0 + 9 +$DIMADEC + 70 + 0 + 9 +$DIMALTRND + 40 +0.0 + 9 +$DIMAZIN + 70 + 0 + 9 +$DIMDSEP + 70 + 46 + 9 +$DIMATFIT + 70 + 3 + 9 +$DIMFRAC + 70 + 0 + 9 +$DIMLDRBLK + 1 + + 9 +$DIMLUNIT + 70 + 2 + 9 +$DIMLWD + 70 + -2 + 9 +$DIMLWE + 70 + -2 + 9 +$DIMTMOVE + 70 + 0 + 9 +$DIMFXL + 40 +1.0 + 9 +$DIMFXLON + 70 + 0 + 9 +$DIMJOGANG + 40 +0.7853981633974483 + 9 +$DIMTFILL + 70 + 0 + 9 +$DIMTFILLCLR + 70 + 0 + 9 +$DIMARCSYM + 70 + 0 + 9 +$DIMLTYPE + 6 + + 9 +$DIMLTEX1 + 6 + + 9 +$DIMLTEX2 + 6 + + 9 +$DIMTXTDIRECTION + 70 + 0 + 9 +$LUNITS + 70 + 2 + 9 +$LUPREC + 70 + 4 + 9 +$SKETCHINC + 40 +0.1 + 9 +$FILLETRAD + 40 +0.5 + 9 +$AUNITS + 70 + 0 + 9 +$AUPREC + 70 + 0 + 9 +$MENU + 1 +. + 9 +$ELEVATION + 40 +0.0 + 9 +$PELEVATION + 40 +0.0 + 9 +$THICKNESS + 40 +0.0 + 9 +$LIMCHECK + 70 + 0 + 9 +$CHAMFERA + 40 +0.5 + 9 +$CHAMFERB + 40 +0.5 + 9 +$CHAMFERC + 40 +1.0 + 9 +$CHAMFERD + 40 +0.0 + 9 +$SKPOLY + 70 + 0 + 9 +$TDCREATE + 40 +2459543.541242199 + 9 +$TDUCREATE + 40 +2459543.291242199 + 9 +$TDUPDATE + 40 +2459543.541242222 + 9 +$TDUUPDATE + 40 +2459543.291242222 + 9 +$TDINDWG + 40 +0.0000000116 + 9 +$TDUSRTIMER + 40 +0.0000000116 + 9 +$USRTIMER + 70 + 1 + 9 +$ANGBASE + 50 +0.0 + 9 +$ANGDIR + 70 + 0 + 9 +$PDMODE + 70 + 0 + 9 +$PDSIZE + 40 +0.0 + 9 +$PLINEWID + 40 +0.0 + 9 +$SPLFRAME + 70 + 0 + 9 +$SPLINETYPE + 70 + 6 + 9 +$SPLINESEGS + 70 + 8 + 9 +$HANDSEED + 5 +4E + 9 +$SURFTAB1 + 70 + 6 + 9 +$SURFTAB2 + 70 + 6 + 9 +$SURFTYPE + 70 + 6 + 9 +$SURFU + 70 + 6 + 9 +$SURFV + 70 + 6 + 9 +$UCSBASE + 2 + + 9 +$UCSNAME + 2 + + 9 +$UCSORG + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSXDIR + 10 +1.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSYDIR + 10 +0.0 + 20 +1.0 + 30 +0.0 + 9 +$UCSORTHOREF + 2 + + 9 +$UCSORTHOVIEW + 70 + 0 + 9 +$UCSORGTOP + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGBOTTOM + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGLEFT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGRIGHT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGFRONT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGBACK + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSBASE + 2 + + 9 +$PUCSNAME + 2 + + 9 +$PUCSORG + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSXDIR + 10 +1.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSYDIR + 10 +0.0 + 20 +1.0 + 30 +0.0 + 9 +$PUCSORTHOREF + 2 + + 9 +$PUCSORTHOVIEW + 70 + 0 + 9 +$PUCSORGTOP + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGBOTTOM + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGLEFT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGRIGHT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGFRONT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGBACK + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$USERI1 + 70 + 0 + 9 +$USERI2 + 70 + 0 + 9 +$USERI3 + 70 + 0 + 9 +$USERI4 + 70 + 0 + 9 +$USERI5 + 70 + 0 + 9 +$USERR1 + 40 +0.0 + 9 +$USERR2 + 40 +0.0 + 9 +$USERR3 + 40 +0.0 + 9 +$USERR4 + 40 +0.0 + 9 +$USERR5 + 40 +0.0 + 9 +$WORLDVIEW + 70 + 1 + 9 +$SHADEDGE + 70 + 3 + 9 +$SHADEDIF + 70 + 70 + 9 +$TILEMODE + 70 + 1 + 9 +$MAXACTVP + 70 + 64 + 9 +$PINSBASE + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PLIMCHECK + 70 + 0 + 9 +$PEXTMIN + 10 +1.000000000000000E+20 + 20 +1.000000000000000E+20 + 30 +1.000000000000000E+20 + 9 +$PEXTMAX + 10 +-1.000000000000000E+20 + 20 +-1.000000000000000E+20 + 30 +-1.000000000000000E+20 + 9 +$PLIMMIN + 10 +0.0 + 20 +0.0 + 9 +$PLIMMAX + 10 +12.0 + 20 +9.0 + 9 +$UNITMODE + 70 + 0 + 9 +$VISRETAIN + 70 + 1 + 9 +$PLINEGEN + 70 + 0 + 9 +$PSLTSCALE + 70 + 1 + 9 +$TREEDEPTH + 70 + 3020 + 9 +$CMLSTYLE + 2 +Standard + 9 +$CMLJUST + 70 + 0 + 9 +$CMLSCALE + 40 +1.0 + 9 +$PROXYGRAPHICS + 70 + 1 + 9 +$MEASUREMENT + 70 + 0 + 9 +$CELWEIGHT +370 + -1 + 9 +$ENDCAPS +280 + 0 + 9 +$JOINSTYLE +280 + 0 + 9 +$LWDISPLAY +290 + 1 + 9 +$INSUNITS + 70 + 1 + 9 +$HYPERLINKBASE + 1 + + 9 +$STYLESHEET + 1 + + 9 +$XEDIT +290 + 1 + 9 +$CEPSNTYPE +380 + 0 + 9 +$PSTYLEMODE +290 + 1 + 9 +$FINGERPRINTGUID + 2 +{DDBEC129-1063-4EE8-8EC0-45D59CB30762} + 9 +$VERSIONGUID + 2 +{FAEB1C32-E019-11D5-929B-00C0DF256EC4} + 9 +$EXTNAMES +290 + 1 + 9 +$PSVPSCALE + 40 +0.0 + 9 +$OLESTARTUP +290 + 0 + 9 +$SORTENTS +280 + 127 + 9 +$INDEXCTL +280 + 0 + 9 +$HIDETEXT +280 + 1 + 9 +$XCLIPFRAME +280 + 2 + 9 +$HALOGAP +280 + 0 + 9 +$OBSCOLOR + 70 + 257 + 9 +$OBSLTYPE +280 + 0 + 9 +$INTERSECTIONDISPLAY +280 + 0 + 9 +$INTERSECTIONCOLOR + 70 + 257 + 9 +$DIMASSOC +280 + 2 + 9 +$PROJECTNAME + 1 + + 9 +$CAMERADISPLAY +290 + 0 + 9 +$LENSLENGTH + 40 +50.0 + 9 +$CAMERAHEIGHT + 40 +0.0 + 9 +$STEPSPERSEC + 40 +2.0 + 9 +$STEPSIZE + 40 +6.0 + 9 +$3DDWFPREC + 40 +2.0 + 9 +$PSOLWIDTH + 40 +0.25 + 9 +$PSOLHEIGHT + 40 +4.0 + 9 +$LOFTANG1 + 40 +1.570796326794897 + 9 +$LOFTANG2 + 40 +1.570796326794897 + 9 +$LOFTMAG1 + 40 +0.0 + 9 +$LOFTMAG2 + 40 +0.0 + 9 +$LOFTPARAM + 70 + 7 + 9 +$LOFTNORMALS +280 + 1 + 9 +$LATITUDE + 40 +37.795 + 9 +$LONGITUDE + 40 +-122.394 + 9 +$NORTHDIRECTION + 40 +0.0 + 9 +$TIMEZONE + 70 + -8000 + 9 +$LIGHTGLYPHDISPLAY +280 + 1 + 9 +$TILEMODELIGHTSYNCH +280 + 1 + 9 +$CMATERIAL +347 +3C + 9 +$SOLIDHIST +280 + 1 + 9 +$SHOWHIST +280 + 1 + 9 +$DWFFRAME +280 + 2 + 9 +$DGNFRAME +280 + 2 + 9 +$REALWORLDSCALE +290 + 1 + 9 +$INTERFERECOLOR + 62 + 256 + 9 +$CSHADOW +280 + 0 + 9 +$SHADOWPLANELOCATION + 40 +0.0 + 0 +ENDSEC + 0 +SECTION + 2 +CLASSES + 0 +CLASS + 1 +ACDBDICTIONARYWDFLT + 2 +AcDbDictionaryWithDefault + 3 +ObjectDBX Classes + 90 + 0 + 91 + 4 +280 + 0 +281 + 0 + 0 +CLASS + 1 +VISUALSTYLE + 2 +AcDbVisualStyle + 3 +ObjectDBX Classes + 90 + 4095 + 91 + 4 +280 + 0 +281 + 0 + 0 +CLASS + 1 +MATERIAL + 2 +AcDbMaterial + 3 +ObjectDBX Classes + 90 + 1153 + 91 + 4 +280 + 0 +281 + 0 + 0 +CLASS + 1 +SUN + 2 +AcDbSun + 3 +SCENEOE + 90 + 1024 + 91 + 4 +280 + 0 +281 + 0 + 0 +ENDSEC + 0 +SECTION + 2 +TABLES + 0 +TABLE + 2 +VPORT + 5 +8 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +VPORT + 5 +29 +330 +8 +100 +AcDbSymbolTableRecord +100 +AcDbViewportTableRecord + 2 +*Active + 70 + 0 + 10 +0.0 + 20 +0.0 + 11 +1.0 + 21 +1.0 + 12 +0.0 + 22 +0.0 + 13 +0.0 + 23 +0.0 + 14 +0.5 + 24 +0.5 + 15 +0.5 + 25 +0.5 + 16 +0.0 + 26 +0.0 + 36 +1.0 + 17 +18.11494034730382 + 27 +-24.92803628315522 + 37 +0.0 + 40 +7.928754340054159 + 41 +1.26123216474023 + 42 +50.0 + 43 +0.0 + 44 +0.0 + 50 +0.0 + 51 +0.0 + 71 + 0 + 72 + 100 + 73 + 1 + 74 + 3 + 75 + 0 + 76 + 0 + 77 + 0 + 78 + 0 +281 + 0 + 65 + 1 +110 +0.0 +120 +0.0 +130 +0.0 +111 +1.0 +121 +0.0 +131 +0.0 +112 +0.0 +122 +1.0 +132 +0.0 + 79 + 0 +146 +0.0 + 60 + 3 + 61 + 5 +292 + 1 +282 + 1 +141 +0.0 +142 +0.0 + 63 + 250 +361 +3F + 0 +ENDTAB + 0 +TABLE + 2 +LTYPE + 5 +5 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +LTYPE + 5 +14 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +ByBlock + 70 + 0 + 3 + + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +LTYPE + 5 +15 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +ByLayer + 70 + 0 + 3 + + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +LTYPE + 5 +16 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +Continuous + 70 + 0 + 3 +Solid line + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +ENDTAB + 0 +TABLE + 2 +LAYER + 5 +2 +330 +0 +100 +AcDbSymbolTable + 70 + 2 + 0 +LAYER + 5 +10 +330 +2 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +0 + 70 + 0 + 62 + 7 + 6 +Continuous +370 + -3 +390 +F +347 +3E + 0 +LAYER + 5 +40 +330 +2 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +Layer 1 + 70 + 0 + 62 + 7 + 6 +Continuous +370 + -3 +390 +F +347 +3E + 0 +ENDTAB + 0 +TABLE + 2 +STYLE + 5 +3 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +STYLE + 5 +11 +330 +3 +100 +AcDbSymbolTableRecord +100 +AcDbTextStyleTableRecord + 2 +Standard + 70 + 0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 + 0 + 42 +0.2 + 3 +txt + 4 + + 0 +ENDTAB + 0 +TABLE + 2 +VIEW + 5 +6 +330 +0 +100 +AcDbSymbolTable + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +UCS + 5 +7 +330 +0 +100 +AcDbSymbolTable + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +APPID + 5 +9 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +APPID + 5 +12 +330 +9 +100 +AcDbSymbolTableRecord +100 +AcDbRegAppTableRecord + 2 +ACAD + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +DIMSTYLE + 5 +A +330 +0 +100 +AcDbSymbolTable + 70 + 1 +100 +AcDbDimStyleTable + 0 +DIMSTYLE +105 +27 +330 +A +100 +AcDbSymbolTableRecord +100 +AcDbDimStyleTableRecord + 2 +Standard + 70 + 0 +178 + 0 +340 +11 + 0 +ENDTAB + 0 +TABLE + 2 +BLOCK_RECORD + 5 +1 +330 +0 +100 +AcDbSymbolTable + 70 + 2 + 0 +BLOCK_RECORD + 5 +1F +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*Model_Space +340 +22 + 70 + 0 +280 + 1 +281 + 0 + 0 +BLOCK_RECORD + 5 +1B +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*Paper_Space +340 +1E + 70 + 0 +280 + 1 +281 + 0 + 0 +BLOCK_RECORD + 5 +23 +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*Paper_Space0 +340 +26 + 70 + 0 +280 + 1 +281 + 0 + 0 +BLOCK_RECORD + 5 +41 +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +block 2 +340 +0 +102 +{BLKREFS +331 +42 +102 +} + 70 + 0 +280 + 1 +281 + 0 + 0 +ENDTAB + 0 +ENDSEC + 0 +SECTION + 2 +BLOCKS + 0 +BLOCK + 5 +20 +330 +1F +100 +AcDbEntity + 8 +0 +100 +AcDbBlockBegin + 2 +*Model_Space + 70 + 0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +*Model_Space + 1 + + 0 +ENDBLK + 5 +21 +330 +1F +100 +AcDbEntity + 8 +0 +100 +AcDbBlockEnd + 0 +BLOCK + 5 +1C +330 +1B +100 +AcDbEntity + 67 + 1 + 8 +0 +100 +AcDbBlockBegin + 2 +*Paper_Space + 70 + 0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +*Paper_Space + 1 + + 0 +ENDBLK + 5 +1D +330 +1B +100 +AcDbEntity + 67 + 1 + 8 +0 +100 +AcDbBlockEnd + 0 +BLOCK + 5 +24 +330 +23 +100 +AcDbEntity + 8 +0 +100 +AcDbBlockBegin + 2 +*Paper_Space0 + 70 + 0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +*Paper_Space0 + 1 + + 0 +ENDBLK + 5 +25 +330 +23 +100 +AcDbEntity + 8 +0 +100 +AcDbBlockEnd + 0 +BLOCK + 5 +43 +330 +41 +100 +AcDbEntity + 8 +0 +100 +AcDbBlockBegin + 2 +block 2 + 70 + 0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +block 2 + 1 + + 0 +SPLINE + 5 +45 +330 +41 +100 +AcDbEntity + 8 +Layer 1 +370 + 0 +100 +AcDbSpline +210 +0.0 +220 +0.0 +230 +1.0 + 70 + 11 + 71 + 3 + 72 + 281 + 73 + 277 + 74 + 0 + 42 +0.0000000001 + 43 +0.0000000001 + 12 +1.0 + 22 +0.0 + 32 +0.0 + 13 +1.0 + 23 +0.0 + 33 +0.0 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +1.0 + 40 +1.0 + 40 +1.0 + 40 +2.0 + 40 +2.0 + 40 +2.0 + 40 +3.0 + 40 +3.0 + 40 +3.0 + 40 +4.0 + 40 +4.0 + 40 +4.0 + 40 +5.0 + 40 +5.0 + 40 +5.0 + 40 +6.0 + 40 +6.0 + 40 +6.0 + 40 +7.0 + 40 +7.0 + 40 +7.0 + 40 +8.0 + 40 +8.0 + 40 +8.0 + 40 +9.0 + 40 +9.0 + 40 +9.0 + 40 +10.0 + 40 +10.0 + 40 +10.0 + 40 +11.0 + 40 +11.0 + 40 +11.0 + 40 +12.0 + 40 +12.0 + 40 +12.0 + 40 +13.0 + 40 +13.0 + 40 +13.0 + 40 +14.0 + 40 +14.0 + 40 +14.0 + 40 +15.0 + 40 +15.0 + 40 +15.0 + 40 +16.0 + 40 +16.0 + 40 +16.0 + 40 +17.0 + 40 +17.0 + 40 +17.0 + 40 +18.0 + 40 +18.0 + 40 +18.0 + 40 +19.0 + 40 +19.0 + 40 +19.0 + 40 +20.0 + 40 +20.0 + 40 +20.0 + 40 +21.0 + 40 +21.0 + 40 +21.0 + 40 +22.0 + 40 +22.0 + 40 +22.0 + 40 +23.0 + 40 +23.0 + 40 +23.0 + 40 +24.0 + 40 +24.0 + 40 +24.0 + 40 +25.0 + 40 +25.0 + 40 +25.0 + 40 +26.0 + 40 +26.0 + 40 +26.0 + 40 +27.0 + 40 +27.0 + 40 +27.0 + 40 +28.0 + 40 +28.0 + 40 +28.0 + 40 +29.0 + 40 +29.0 + 40 +29.0 + 40 +30.0 + 40 +30.0 + 40 +30.0 + 40 +31.0 + 40 +31.0 + 40 +31.0 + 40 +32.0 + 40 +32.0 + 40 +32.0 + 40 +33.0 + 40 +33.0 + 40 +33.0 + 40 +34.0 + 40 +34.0 + 40 +34.0 + 40 +35.0 + 40 +35.0 + 40 +35.0 + 40 +36.0 + 40 +36.0 + 40 +36.0 + 40 +37.0 + 40 +37.0 + 40 +37.0 + 40 +38.0 + 40 +38.0 + 40 +38.0 + 40 +39.0 + 40 +39.0 + 40 +39.0 + 40 +40.0 + 40 +40.0 + 40 +40.0 + 40 +41.0 + 40 +41.0 + 40 +41.0 + 40 +42.0 + 40 +42.0 + 40 +42.0 + 40 +43.0 + 40 +43.0 + 40 +43.0 + 40 +44.0 + 40 +44.0 + 40 +44.0 + 40 +45.0 + 40 +45.0 + 40 +45.0 + 40 +46.0 + 40 +46.0 + 40 +46.0 + 40 +47.0 + 40 +47.0 + 40 +47.0 + 40 +48.0 + 40 +48.0 + 40 +48.0 + 40 +49.0 + 40 +49.0 + 40 +49.0 + 40 +50.0 + 40 +50.0 + 40 +50.0 + 40 +51.0 + 40 +51.0 + 40 +51.0 + 40 +52.0 + 40 +52.0 + 40 +52.0 + 40 +53.0 + 40 +53.0 + 40 +53.0 + 40 +54.0 + 40 +54.0 + 40 +54.0 + 40 +55.0 + 40 +55.0 + 40 +55.0 + 40 +56.0 + 40 +56.0 + 40 +56.0 + 40 +57.0 + 40 +57.0 + 40 +57.0 + 40 +58.0 + 40 +58.0 + 40 +58.0 + 40 +59.0 + 40 +59.0 + 40 +59.0 + 40 +60.0 + 40 +60.0 + 40 +60.0 + 40 +61.0 + 40 +61.0 + 40 +61.0 + 40 +62.0 + 40 +62.0 + 40 +62.0 + 40 +63.0 + 40 +63.0 + 40 +63.0 + 40 +64.0 + 40 +64.0 + 40 +64.0 + 40 +65.0 + 40 +65.0 + 40 +65.0 + 40 +66.0 + 40 +66.0 + 40 +66.0 + 40 +67.0 + 40 +67.0 + 40 +67.0 + 40 +68.0 + 40 +68.0 + 40 +68.0 + 40 +69.0 + 40 +69.0 + 40 +69.0 + 40 +70.0 + 40 +70.0 + 40 +70.0 + 40 +71.0 + 40 +71.0 + 40 +71.0 + 40 +72.0 + 40 +72.0 + 40 +72.0 + 40 +73.0 + 40 +73.0 + 40 +73.0 + 40 +74.0 + 40 +74.0 + 40 +74.0 + 40 +75.0 + 40 +75.0 + 40 +75.0 + 40 +76.0 + 40 +76.0 + 40 +76.0 + 40 +77.0 + 40 +77.0 + 40 +77.0 + 40 +78.0 + 40 +78.0 + 40 +78.0 + 40 +79.0 + 40 +79.0 + 40 +79.0 + 40 +80.0 + 40 +80.0 + 40 +80.0 + 40 +81.0 + 40 +81.0 + 40 +81.0 + 40 +82.0 + 40 +82.0 + 40 +82.0 + 40 +83.0 + 40 +83.0 + 40 +83.0 + 40 +84.0 + 40 +84.0 + 40 +84.0 + 40 +85.0 + 40 +85.0 + 40 +85.0 + 40 +86.0 + 40 +86.0 + 40 +86.0 + 40 +87.0 + 40 +87.0 + 40 +87.0 + 40 +88.0 + 40 +88.0 + 40 +88.0 + 40 +89.0 + 40 +89.0 + 40 +89.0 + 40 +90.0 + 40 +90.0 + 40 +90.0 + 40 +91.0 + 40 +91.0 + 40 +91.0 + 40 +92.0 + 40 +92.0 + 40 +92.0 + 40 +92.0 + 10 +23.07799777800786 + 20 +-22.63135674759943 + 30 +0.0 + 10 +23.07799777800786 + 20 +-22.63135674759943 + 30 +0.0 + 10 +23.06002689005239 + 20 +-22.59182057472617 + 30 +0.0 + 10 +23.06002689005239 + 20 +-22.59182057472617 + 30 +0.0 + 10 +23.06002689005239 + 20 +-22.59182057472617 + 30 +0.0 + 10 +23.05643358994606 + 20 +-22.58103848069524 + 30 +0.0 + 10 +23.05643358994606 + 20 +-22.58103848069524 + 30 +0.0 + 10 +23.05643358994606 + 20 +-22.58103848069524 + 30 +0.0 + 10 +23.05284028983973 + 20 +-22.57384968677065 + 30 +0.0 + 10 +23.05284028983973 + 20 +-22.57384968677065 + 30 +0.0 + 10 +23.05284028983973 + 20 +-22.57384968677065 + 30 +0.0 + 10 +23.04565149591518 + 20 +-22.55947319577743 + 30 +0.0 + 10 +23.04565149591518 + 20 +-22.55947319577743 + 30 +0.0 + 10 +23.04565149591518 + 20 +-22.55947319577743 + 30 +0.0 + 10 +23.03846270199059 + 20 +-22.54509670478425 + 30 +0.0 + 10 +23.03846270199059 + 20 +-22.54509670478425 + 30 +0.0 + 10 +23.03846270199059 + 20 +-22.54509670478425 + 30 +0.0 + 10 +23.03486940188425 + 20 +-22.53790791085965 + 30 +0.0 + 10 +23.03486940188425 + 20 +-22.53790791085965 + 30 +0.0 + 10 +23.03486940188425 + 20 +-22.53790791085965 + 30 +0.0 + 10 +23.03486940188425 + 20 +-22.53431351389736 + 30 +0.0 + 10 +23.03486940188425 + 20 +-22.53431351389736 + 30 +0.0 + 10 +23.02768060795966 + 20 +-22.51993702290418 + 30 +0.0 + 10 +23.03127610177792 + 20 +-22.52712471997281 + 30 +0.0 + 10 +23.02768060795966 + 20 +-22.52353141986643 + 30 +0.0 + 10 +23.02768060795966 + 20 +-22.52353141986643 + 30 +0.0 + 10 +23.00611641989781 + 20 +-22.49118404091771 + 30 +0.0 + 10 +23.00611641989781 + 20 +-22.49118404091771 + 30 +0.0 + 10 +22.99173883204864 + 20 +-22.46961875599994 + 30 +0.0 + 10 +22.97376794409317 + 20 +-22.45164786804445 + 30 +0.0 + 10 +22.95939254995591 + 20 +-22.43008258312663 + 30 +0.0 + 10 +22.94142166200037 + 20 +-22.41211169517116 + 30 +0.0 + 10 +22.92345077404484 + 20 +-22.39414080721563 + 30 +0.0 + 10 +22.90547988608937 + 20 +-22.37616991926016 + 30 +0.0 + 10 +22.75093068841448 + 20 +-22.23959160954074 + 30 +0.0 + 10 +22.52449793891754 + 20 +-22.19286664274285 + 30 +0.0 + 10 +22.33041147151337 + 20 +-22.25396810053391 + 30 +0.0 + 10 +22.30525178963325 + 20 +-22.26115689445851 + 30 +0.0 + 10 +22.27649880764688 + 20 +-22.27193898848943 + 30 +0.0 + 10 +22.25133912576676 + 20 +-22.28272108252036 + 30 +0.0 + 10 +22.25133912576676 + 20 +-22.28272108252036 + 30 +0.0 + 10 +22.2261794438867 + 20 +-22.29709757351356 + 30 +0.0 + 10 +22.2261794438867 + 20 +-22.29709757351356 + 30 +0.0 + 10 +22.2261794438867 + 20 +-22.29709757351356 + 30 +0.0 + 10 +22.21180185603756 + 20 +-22.30428636743816 + 30 +0.0 + 10 +22.21180185603756 + 20 +-22.30428636743816 + 30 +0.0 + 10 +22.21180185603756 + 20 +-22.30428636743816 + 30 +0.0 + 10 +22.20101976200661 + 20 +-22.31147516136275 + 30 +0.0 + 10 +22.20101976200661 + 20 +-22.31147516136275 + 30 +0.0 + 10 +22.20101976200661 + 20 +-22.31147516136275 + 30 +0.0 + 10 +22.19383096808201 + 20 +-22.315069558325 + 30 +0.0 + 10 +22.19383096808201 + 20 +-22.315069558325 + 30 +0.0 + 10 +22.19383096808201 + 20 +-22.315069558325 + 30 +0.0 + 10 +22.0931944342736 + 20 +-22.37617101611613 + 30 +0.0 + 10 +22.0931944342736 + 20 +-22.37617101611613 + 30 +0.0 + 10 +22.0931944342736 + 20 +-22.37617101611613 + 30 +0.0 + 10 +21.88832587283853 + 20 +-22.49477843788001 + 30 +0.0 + 10 +21.88832587283853 + 20 +-22.49477843788001 + 30 +0.0 + 10 +21.88832587283853 + 20 +-22.49477843788001 + 30 +0.0 + 10 +21.48218424378658 + 20 +-22.73199437826381 + 30 +0.0 + 10 +21.48218424378658 + 20 +-22.73199437826381 + 30 +0.0 + 10 +21.48218424378658 + 20 +-22.73199437826381 + 30 +0.0 + 10 +21.20183883042316 + 20 +-22.89373236986335 + 30 +0.0 + 10 +21.20183883042316 + 20 +-22.89373236986335 + 30 +0.0 + 10 +21.20183883042316 + 20 +-22.72839998130151 + 30 +0.0 + 10 +21.20183883042316 + 20 +-22.55947429263339 + 30 +0.0 + 10 +21.20543213052949 + 20 +-22.3905475071093 + 30 +0.0 + 10 +21.20543213052949 + 20 +-22.28991097330089 + 30 +0.0 + 10 +21.20543213052949 + 20 +-22.19286773959882 + 30 +0.0 + 10 +21.20543213052949 + 20 +-22.09223120579039 + 30 +0.0 + 10 +21.20543213052949 + 20 +-22.04191293888617 + 30 +0.0 + 10 +21.20543213052949 + 20 +-21.99159467198198 + 30 +0.0 + 10 +21.20543213052949 + 20 +-21.94127530822177 + 30 +0.0 + 10 +21.20543213052949 + 20 +-21.91252232623538 + 30 +0.0 + 10 +21.20183883042316 + 20 +-21.88376824739301 + 30 +0.0 + 10 +21.20183883042316 + 20 +-21.85501526540659 + 30 +0.0 + 10 +21.20183883042316 + 20 +-21.82626228342019 + 30 +0.0 + 10 +21.19465003649859 + 20 +-21.79750820457778 + 30 +0.0 + 10 +21.19105673639223 + 20 +-21.76875522259136 + 30 +0.0 + 10 +21.15152166037488 + 20 +-21.53513367916985 + 30 +0.0 + 10 +21.02212995086814 + 20 +-21.31588862674158 + 30 +0.0 + 10 +20.82804458031993 + 20 +-21.16852767456324 + 30 +0.0 + 10 +20.73100134661786 + 20 +-21.09304972577898 + 30 +0.0 + 10 +20.62317601888486 + 20 +-21.03913706191245 + 30 +0.0 + 10 +20.5045685971209 + 20 +-21.00319528600145 + 30 +0.0 + 10 +20.4758156151345 + 20 +-20.99600704050484 + 30 +0.0 + 10 +20.44706153629214 + 20 +-20.98522439804595 + 30 +0.0 + 10 +20.41471415734342 + 20 +-20.98163000108368 + 30 +0.0 + 10 +20.38236677839473 + 20 +-20.97803560412138 + 30 +0.0 + 10 +20.34642500248371 + 20 +-20.97084735862477 + 30 +0.0 + 10 +20.31767092364134 + 20 +-20.96725351009043 + 30 +0.0 + 10 +20.31767092364134 + 20 +-20.96725351009043 + 30 +0.0 + 10 +20.23859857789474 + 20 +-20.96365911312813 + 30 +0.0 + 10 +20.23859857789474 + 20 +-20.96365911312813 + 30 +0.0 + 10 +20.23859857789474 + 20 +-20.96365911312813 + 30 +0.0 + 10 +20.15233853507955 + 20 +-20.96365911312813 + 30 +0.0 + 10 +20.15233853507955 + 20 +-20.96365911312813 + 30 +0.0 + 10 +19.94028227657582 + 20 +-20.96365911312813 + 30 +0.0 + 10 +19.72822601807211 + 20 +-20.96725351009043 + 30 +0.0 + 10 +19.51257536260608 + 20 +-20.96725351009043 + 30 +0.0 + 10 +18.64997274074209 + 20 +-20.97084790705273 + 30 +0.0 + 10 +17.75861713689164 + 20 +-20.98522439804595 + 30 +0.0 + 10 +16.85288394519201 + 20 +-20.99241264354254 + 30 +0.0 + 10 +15.94715075349237 + 20 +-21.00319528600145 + 30 +0.0 + 10 +15.02704216765551 + 20 +-21.00678913453577 + 30 +0.0 + 10 +14.1069324849627 + 20 +-21.00678913453577 + 30 +0.0 + 10 +13.84815125966108 + 20 +-21.00319473757347 + 30 +0.0 + 10 +13.59296552817775 + 20 +-21.11820885923104 + 30 +0.0 + 10 +13.42044434569137 + 20 +-21.30510543585468 + 30 +0.0 + 10 +13.33418430287619 + 20 +-21.39855427259448 + 30 +0.0 + 10 +13.26589514801645 + 20 +-21.50997399728979 + 30 +0.0 + 10 +13.22276457818089 + 20 +-21.63217581601599 + 30 +0.0 + 10 +13.20119929326309 + 20 +-21.69327727380707 + 30 +0.0 + 10 +13.18682280226984 + 20 +-21.75437763474222 + 30 +0.0 + 10 +13.17604070823894 + 20 +-21.82266678960193 + 30 +0.0 + 10 +13.17604070823894 + 20 +-21.82985558352652 + 30 +0.0 + 10 +13.17244631127664 + 20 +-21.8406376775574 + 30 +0.0 + 10 +13.17244631127664 + 20 +-21.84782647148199 + 30 +0.0 + 10 +13.17244631127664 + 20 +-21.84782647148199 + 30 +0.0 + 10 +13.17244631127664 + 20 +-21.86939175639979 + 30 +0.0 + 10 +13.17244631127664 + 20 +-21.86939175639979 + 30 +0.0 + 10 +13.17244631127664 + 20 +-21.86939175639979 + 30 +0.0 + 10 +13.16885191431435 + 20 +-21.91252232623538 + 30 +0.0 + 10 +13.16885191431435 + 20 +-21.91252232623538 + 30 +0.0 + 10 +13.16885191431435 + 20 +-21.91252232623538 + 30 +0.0 + 10 +13.16885191431435 + 20 +-21.9233044202663 + 30 +0.0 + 10 +13.16885191431435 + 20 +-21.9233044202663 + 30 +0.0 + 10 +13.16885191431435 + 20 +-21.9233044202663 + 30 +0.0 + 10 +13.16885191431435 + 20 +-21.93768091125952 + 30 +0.0 + 10 +13.16885191431435 + 20 +-21.93768091125952 + 30 +0.0 + 10 +13.16885191431435 + 20 +-21.93768091125952 + 30 +0.0 + 10 +13.16885191431435 + 20 +-22.00237566915694 + 30 +0.0 + 10 +13.16885191431435 + 20 +-22.00237566915694 + 30 +0.0 + 10 +13.16885191431435 + 20 +-22.46242996207535 + 30 +0.0 + 10 +13.1616631203898 + 20 +-22.91529655792515 + 30 +0.0 + 10 +13.15806982028347 + 20 +-23.37175755073728 + 30 +0.0 + 10 +13.14728772625254 + 20 +-24.27749074243689 + 30 +0.0 + 10 +13.14009893232795 + 20 +-25.16884634628731 + 30 +0.0 + 10 +13.12931683829702 + 20 +-26.03144896815135 + 30 +0.0 + 10 +13.12572244133475 + 20 +-26.46275027908339 + 30 +0.0 + 10 +13.12212804437243 + 20 +-26.88686279609081 + 30 +0.0 + 10 +13.1185347442661 + 20 +-27.30378761602962 + 30 +0.0 + 10 +13.11494034730382 + 20 +-27.51224947757105 + 30 +0.0 + 10 +13.11494034730382 + 20 +-27.71711803900613 + 30 +0.0 + 10 +13.11494034730382 + 20 +-27.92198660044121 + 30 +0.0 + 10 +13.11494034730382 + 20 +-28.04059402220517 + 30 +0.0 + 10 +13.14010002918386 + 20 +-28.16279693778733 + 30 +0.0 + 10 +13.18322950216351 + 20 +-28.27421556562667 + 30 +0.0 + 10 +13.2263600719991 + 20 +-28.38563529032191 + 30 +0.0 + 10 +13.29105482989656 + 20 +-28.4862718241304 + 30 +0.0 + 10 +13.37012717564312 + 20 +-28.57253186694558 + 30 +0.0 + 10 +13.44919952138967 + 20 +-28.65879190976082 + 30 +0.0 + 10 +13.54264726127354 + 20 +-28.7306754615828 + 30 +0.0 + 10 +13.64328489193791 + 20 +-28.78458812544929 + 30 +0.0 + 10 +13.64328489193791 + 20 +-28.78458812544929 + 30 +0.0 + 10 +13.72235723768452 + 20 +-28.82052990136031 + 30 +0.0 + 10 +13.72235723768452 + 20 +-28.82052990136031 + 30 +0.0 + 10 +13.75111021967091 + 20 +-28.83131199539124 + 30 +0.0 + 10 +13.776269901551 + 20 +-28.83850078931583 + 30 +0.0 + 10 +13.80502288353745 + 20 +-28.84928288334671 + 30 +0.0 + 10 +13.85893554740394 + 20 +-28.86365937433993 + 30 +0.0 + 10 +13.91284821127043 + 20 +-28.87803586533315 + 30 +0.0 + 10 +13.97754296916784 + 20 +-28.8852246592577 + 30 +0.0 + 10 +13.99191946016107 + 20 +-28.88881905622 + 30 +0.0 + 10 +14.00989034811654 + 20 +-28.88881905622 + 30 +0.0 + 10 +14.02067353900343 + 20 +-28.89241345318229 + 30 +0.0 + 10 +14.02067353900343 + 20 +-28.89241345318229 + 30 +0.0 + 10 +14.05661531491442 + 20 +-28.89241345318229 + 30 +0.0 + 10 +14.05661531491442 + 20 +-28.89241345318229 + 30 +0.0 + 10 +14.05661531491442 + 20 +-28.89241345318229 + 30 +0.0 + 10 +14.09255709082544 + 20 +-28.89241345318229 + 30 +0.0 + 10 +14.09255709082544 + 20 +-28.89241345318229 + 30 +0.0 + 10 +14.09255709082544 + 20 +-28.89241345318229 + 30 +0.0 + 10 +14.10333918485632 + 20 +-28.89241345318229 + 30 +0.0 + 10 +14.10333918485632 + 20 +-28.89241345318229 + 30 +0.0 + 10 +14.12131007281184 + 20 +-28.89241345318229 + 30 +0.0 + 10 +14.11412127888724 + 20 +-28.89241345318229 + 30 +0.0 + 10 +14.12131007281184 + 20 +-28.89241345318229 + 30 +0.0 + 10 +14.12131007281184 + 20 +-28.89241345318229 + 30 +0.0 + 10 +14.13928096076736 + 20 +-28.89241345318229 + 30 +0.0 + 10 +14.13928096076736 + 20 +-28.89241345318229 + 30 +0.0 + 10 +14.18600483070925 + 20 +-28.89241345318229 + 30 +0.0 + 10 +14.23272979750714 + 20 +-28.89241345318229 + 30 +0.0 + 10 +14.28304806441138 + 20 +-28.89241345318229 + 30 +0.0 + 10 +14.37649690115116 + 20 +-28.89241345318229 + 30 +0.0 + 10 +14.4735390379973 + 20 +-28.89241345318229 + 30 +0.0 + 10 +14.5633934777748 + 20 +-28.88881905622 + 30 +0.0 + 10 +14.93718772787803 + 20 +-28.8852246592577 + 30 +0.0 + 10 +15.29660548698803 + 20 +-28.8816302622954 + 30 +0.0 + 10 +15.64164675510483 + 20 +-28.87803696218907 + 30 +0.0 + 10 +16.33172929133843 + 20 +-28.87084816826453 + 30 +0.0 + 10 +16.96430366988731 + 20 +-28.8636604711959 + 30 +0.0 + 10 +17.52499559347012 + 20 +-28.8564716772713 + 30 +0.0 + 10 +18.08568751705287 + 20 +-28.84928288334671 + 30 +0.0 + 10 +18.57449588881375 + 20 +-28.84568958324038 + 30 +0.0 + 10 +18.97704312090337 + 20 +-28.84568958324038 + 30 +0.0 + 10 +19.7821386819386 + 20 +-28.84209518627808 + 30 +0.0 + 10 +20.242194071713 + 20 +-28.83850078931583 + 30 +0.0 + 10 +20.242194071713 + 20 +-28.83850078931583 + 30 +0.0 + 10 +20.242194071713 + 20 +-28.83850078931583 + 30 +0.0 + 10 +20.27454145066172 + 20 +-28.83850078931583 + 30 +0.0 + 10 +20.33564290845278 + 20 +-28.83490639235353 + 30 +0.0 + 10 +20.39674436624387 + 20 +-28.82412429832261 + 30 +0.0 + 10 +20.49019210612773 + 20 +-28.81334110743571 + 30 +0.0 + 10 +20.60161183082303 + 20 +-28.76661723749382 + 30 +0.0 + 10 +20.7130315555183 + 20 +-28.71989336755188 + 30 +0.0 + 10 +20.84242216816907 + 20 +-28.637226624843 + 30 +0.0 + 10 +20.95384079600843 + 20 +-28.50424271208587 + 30 +0.0 + 10 +21.00775345987495 + 20 +-28.43595355722616 + 30 +0.0 + 10 +21.06166612374144 + 20 +-28.35688121147955 + 30 +0.0 + 10 +21.10120229661475 + 20 +-28.26702677170207 + 30 +0.0 + 10 +21.10839109053935 + 20 +-28.24186708982198 + 30 +0.0 + 10 +21.11917318457027 + 20 +-28.22030290176013 + 30 +0.0 + 10 +21.12636197849482 + 20 +-28.19514321988006 + 30 +0.0 + 10 +21.13355077241936 + 20 +-28.169983538 + 30 +0.0 + 10 +21.14073956634396 + 20 +-28.14482495297587 + 30 +0.0 + 10 +21.14792616655662 + 20 +-28.11966527109576 + 30 +0.0 + 10 +21.15511496048121 + 20 +-28.09450558921569 + 30 +0.0 + 10 +21.15870826058755 + 20 +-28.06575260722926 + 30 +0.0 + 10 +21.16230375440581 + 20 +-28.03699962524287 + 30 +0.0 + 10 +21.16589705451214 + 20 +-28.02262313424964 + 30 +0.0 + 10 +21.16589705451214 + 20 +-28.00824664325647 + 30 +0.0 + 10 +21.1694925483304 + 20 +-27.99386905540728 + 30 +0.0 + 10 +21.1694925483304 + 20 +-27.98308696137635 + 30 +0.0 + 10 +21.1694925483304 + 20 +-27.96870937352719 + 30 +0.0 + 10 +21.1694925483304 + 20 +-27.95792727949626 + 30 +0.0 + 10 +21.1694925483304 + 20 +-27.94714518546533 + 30 +0.0 + 10 +21.1694925483304 + 20 +-27.93276759761619 + 30 +0.0 + 10 +21.17308584843673 + 20 +-27.91839110662297 + 30 +0.0 + 10 +21.17308584843673 + 20 +-27.91839110662297 + 30 +0.0 + 10 +21.17308584843673 + 20 +-27.90401461562977 + 30 +0.0 + 10 +21.17308584843673 + 20 +-27.90401461562977 + 30 +0.0 + 10 +21.17308584843673 + 20 +-27.90401461562977 + 30 +0.0 + 10 +21.17308584843673 + 20 +-27.87526163364338 + 30 +0.0 + 10 +21.17308584843673 + 20 +-27.87526163364338 + 30 +0.0 + 10 +21.17308584843673 + 20 +-27.85010195176326 + 30 +0.0 + 10 +21.17308584843673 + 20 +-27.82494336673913 + 30 +0.0 + 10 +21.17308584843673 + 20 +-27.79618928789677 + 30 +0.0 + 10 +21.17308584843673 + 20 +-27.74227662403028 + 30 +0.0 + 10 +21.17308584843673 + 20 +-27.68836396016377 + 30 +0.0 + 10 +21.17308584843673 + 20 +-27.63445129629728 + 30 +0.0 + 10 +21.17308584843673 + 20 +-27.52303157160198 + 30 +0.0 + 10 +21.17308584843673 + 20 +-27.40801854680032 + 30 +0.0 + 10 +21.17308584843673 + 20 +-27.28581563121816 + 30 +0.0 + 10 +21.17308584843673 + 20 +-27.04141199376575 + 30 +0.0 + 10 +21.17667914854307 + 20 +-26.77903637150186 + 30 +0.0 + 10 +21.17667914854307 + 20 +-26.49869095813844 + 30 +0.0 + 10 +21.18027244864942 + 20 +-25.93799903455561 + 30 +0.0 + 10 +21.18746124257402 + 20 +-25.30542355915082 + 30 +0.0 + 10 +21.19105673639223 + 20 +-24.61534211977318 + 30 +0.0 + 10 +21.19105673639223 + 20 +-24.51111118900245 + 30 +0.0 + 10 +21.19465003649859 + 20 +-24.40328586126945 + 30 +0.0 + 10 +21.19465003649859 + 20 +-24.29546053353645 + 30 +0.0 + 10 +21.19465003649859 + 20 +-24.29546053353645 + 30 +0.0 + 10 +21.53609690765312 + 20 +-24.09778076602593 + 30 +0.0 + 10 +21.53609690765312 + 20 +-24.09778076602593 + 30 +0.0 + 10 +21.53609690765312 + 20 +-24.09778076602593 + 30 +0.0 + 10 +22.35197565957522 + 20 +-23.62334888525834 + 30 +0.0 + 10 +22.35197565957522 + 20 +-23.62334888525834 + 30 +0.0 + 10 +22.35197565957522 + 20 +-23.62334888525834 + 30 +0.0 + 10 +22.75811728862714 + 20 +-23.38613294487454 + 30 +0.0 + 10 +22.75811728862714 + 20 +-23.38613294487454 + 30 +0.0 + 10 +22.75811728862714 + 20 +-23.38613294487454 + 30 +0.0 + 10 +22.8084366523873 + 20 +-23.35737996288809 + 30 +0.0 + 10 +22.8084366523873 + 20 +-23.35737996288809 + 30 +0.0 + 10 +22.82640754034282 + 20 +-23.34659786885721 + 30 +0.0 + 10 +22.85156722222286 + 20 +-23.32862698090169 + 30 +0.0 + 10 +22.87313141028471 + 20 +-23.31424939305253 + 30 +0.0 + 10 +22.91266648630209 + 20 +-23.28190201410383 + 30 +0.0 + 10 +22.95220375603132 + 20 +-23.24596023819281 + 30 +0.0 + 10 +22.98455003812404 + 20 +-23.20642406531952 + 30 +0.0 + 10 +23.04924479602151 + 20 +-23.12735171957292 + 30 +0.0 + 10 +23.09237536585705 + 20 +-23.02671518576451 + 30 +0.0 + 10 +23.11034625381257 + 20 +-22.92607865195608 + 30 +0.0 + 10 +23.1211283478435 + 20 +-22.83263091207224 + 30 +0.0 + 10 +23.1139395539189 + 20 +-22.72839998130151 + 30 +0.0 + 10 +23.07799777800786 + 20 +-22.63135674759943 + 30 +0.0 + 0 +SPLINE + 5 +46 +330 +41 +100 +AcDbEntity + 8 +Layer 1 +370 + 0 +100 +AcDbSpline +210 +0.0 +220 +0.0 +230 +1.0 + 70 + 11 + 71 + 3 + 72 + 212 + 73 + 208 + 74 + 0 + 42 +0.0000000001 + 43 +0.0000000001 + 12 +1.0 + 22 +0.0 + 32 +0.0 + 13 +1.0 + 23 +0.0 + 33 +0.0 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +1.0 + 40 +1.0 + 40 +1.0 + 40 +2.0 + 40 +2.0 + 40 +2.0 + 40 +3.0 + 40 +3.0 + 40 +3.0 + 40 +4.0 + 40 +4.0 + 40 +4.0 + 40 +5.0 + 40 +5.0 + 40 +5.0 + 40 +6.0 + 40 +6.0 + 40 +6.0 + 40 +7.0 + 40 +7.0 + 40 +7.0 + 40 +8.0 + 40 +8.0 + 40 +8.0 + 40 +9.0 + 40 +9.0 + 40 +9.0 + 40 +10.0 + 40 +10.0 + 40 +10.0 + 40 +11.0 + 40 +11.0 + 40 +11.0 + 40 +12.0 + 40 +12.0 + 40 +12.0 + 40 +13.0 + 40 +13.0 + 40 +13.0 + 40 +14.0 + 40 +14.0 + 40 +14.0 + 40 +15.0 + 40 +15.0 + 40 +15.0 + 40 +16.0 + 40 +16.0 + 40 +16.0 + 40 +17.0 + 40 +17.0 + 40 +17.0 + 40 +18.0 + 40 +18.0 + 40 +18.0 + 40 +19.0 + 40 +19.0 + 40 +19.0 + 40 +20.0 + 40 +20.0 + 40 +20.0 + 40 +21.0 + 40 +21.0 + 40 +21.0 + 40 +22.0 + 40 +22.0 + 40 +22.0 + 40 +23.0 + 40 +23.0 + 40 +23.0 + 40 +24.0 + 40 +24.0 + 40 +24.0 + 40 +25.0 + 40 +25.0 + 40 +25.0 + 40 +26.0 + 40 +26.0 + 40 +26.0 + 40 +27.0 + 40 +27.0 + 40 +27.0 + 40 +28.0 + 40 +28.0 + 40 +28.0 + 40 +29.0 + 40 +29.0 + 40 +29.0 + 40 +30.0 + 40 +30.0 + 40 +30.0 + 40 +31.0 + 40 +31.0 + 40 +31.0 + 40 +32.0 + 40 +32.0 + 40 +32.0 + 40 +33.0 + 40 +33.0 + 40 +33.0 + 40 +34.0 + 40 +34.0 + 40 +34.0 + 40 +35.0 + 40 +35.0 + 40 +35.0 + 40 +36.0 + 40 +36.0 + 40 +36.0 + 40 +37.0 + 40 +37.0 + 40 +37.0 + 40 +38.0 + 40 +38.0 + 40 +38.0 + 40 +39.0 + 40 +39.0 + 40 +39.0 + 40 +40.0 + 40 +40.0 + 40 +40.0 + 40 +41.0 + 40 +41.0 + 40 +41.0 + 40 +42.0 + 40 +42.0 + 40 +42.0 + 40 +43.0 + 40 +43.0 + 40 +43.0 + 40 +44.0 + 40 +44.0 + 40 +44.0 + 40 +45.0 + 40 +45.0 + 40 +45.0 + 40 +46.0 + 40 +46.0 + 40 +46.0 + 40 +47.0 + 40 +47.0 + 40 +47.0 + 40 +48.0 + 40 +48.0 + 40 +48.0 + 40 +49.0 + 40 +49.0 + 40 +49.0 + 40 +50.0 + 40 +50.0 + 40 +50.0 + 40 +51.0 + 40 +51.0 + 40 +51.0 + 40 +52.0 + 40 +52.0 + 40 +52.0 + 40 +53.0 + 40 +53.0 + 40 +53.0 + 40 +54.0 + 40 +54.0 + 40 +54.0 + 40 +55.0 + 40 +55.0 + 40 +55.0 + 40 +56.0 + 40 +56.0 + 40 +56.0 + 40 +57.0 + 40 +57.0 + 40 +57.0 + 40 +58.0 + 40 +58.0 + 40 +58.0 + 40 +59.0 + 40 +59.0 + 40 +59.0 + 40 +60.0 + 40 +60.0 + 40 +60.0 + 40 +61.0 + 40 +61.0 + 40 +61.0 + 40 +62.0 + 40 +62.0 + 40 +62.0 + 40 +63.0 + 40 +63.0 + 40 +63.0 + 40 +64.0 + 40 +64.0 + 40 +64.0 + 40 +65.0 + 40 +65.0 + 40 +65.0 + 40 +66.0 + 40 +66.0 + 40 +66.0 + 40 +67.0 + 40 +67.0 + 40 +67.0 + 40 +68.0 + 40 +68.0 + 40 +68.0 + 40 +69.0 + 40 +69.0 + 40 +69.0 + 40 +69.0 + 10 +18.22945242698501 + 20 +-24.65128499254009 + 30 +0.0 + 10 +18.22945242698501 + 20 +-24.65128499254009 + 30 +0.0 + 10 +16.68036166584966 + 20 +-25.56780027827065 + 30 +0.0 + 10 +16.68036166584966 + 20 +-25.56780027827065 + 30 +0.0 + 10 +16.56175424408573 + 20 +-25.41325108059573 + 30 +0.0 + 10 +16.44314572546586 + 20 +-25.25870078606486 + 30 +0.0 + 10 +16.32453830370193 + 20 +-25.10774598535221 + 30 +0.0 + 10 +16.32453830370193 + 20 +-25.10774598535221 + 30 +0.0 + 10 +15.91480227768774 + 20 +-24.57580704375583 + 30 +0.0 + 10 +15.91480227768774 + 20 +-24.57580704375583 + 30 +0.0 + 10 +15.91480227768774 + 20 +-24.57580704375583 + 30 +0.0 + 10 +15.81057134691703 + 20 +-24.44641643110501 + 30 +0.0 + 10 +15.81057134691703 + 20 +-24.44641643110501 + 30 +0.0 + 10 +15.79978925288611 + 20 +-24.43563433707411 + 30 +0.0 + 10 +15.79619485592381 + 20 +-24.42485114618722 + 30 +0.0 + 10 +15.78181836493059 + 20 +-24.41047465519404 + 30 +0.0 + 10 +15.77103627089966 + 20 +-24.39609816420082 + 30 +0.0 + 10 +15.76025308001279 + 20 +-24.38172167320759 + 30 +0.0 + 10 +15.74587658901957 + 20 +-24.37093848232073 + 30 +0.0 + 10 +15.74587658901957 + 20 +-24.37093848232073 + 30 +0.0 + 10 +15.7063404161463 + 20 +-24.33499670640973 + 30 +0.0 + 10 +15.7063404161463 + 20 +-24.33499670640973 + 30 +0.0 + 10 +15.69196392515308 + 20 +-24.32421461237881 + 30 +0.0 + 10 +15.67758743415986 + 20 +-24.31343142149192 + 30 +0.0 + 10 +15.66320984631066 + 20 +-24.30264932746104 + 30 +0.0 + 10 +15.54460242454679 + 20 +-24.21998368160808 + 30 +0.0 + 10 +15.39724092394052 + 20 +-24.18044750873479 + 30 +0.0 + 10 +15.25706821725875 + 20 +-24.19841839669031 + 30 +0.0 + 10 +15.18518466543674 + 20 +-24.20560719061485 + 30 +0.0 + 10 +15.11330111361475 + 20 +-24.22717137867673 + 30 +0.0 + 10 +15.04860635571734 + 20 +-24.25951985448139 + 30 +0.0 + 10 +15.03063546776184 + 20 +-24.27030194851232 + 30 +0.0 + 10 +15.01266457980637 + 20 +-24.27749074243689 + 30 +0.0 + 10 +14.99469369185085 + 20 +-24.28827283646782 + 30 +0.0 + 10 +14.98391159781992 + 20 +-24.29546163039241 + 30 +0.0 + 10 +14.98031720085767 + 20 +-24.29905493049874 + 30 +0.0 + 10 +14.97672280389533 + 20 +-24.30264932746104 + 30 +0.0 + 10 +14.97672280389533 + 20 +-24.30264932746104 + 30 +0.0 + 10 +14.95875191593985 + 20 +-24.31343142149192 + 30 +0.0 + 10 +14.95875191593985 + 20 +-24.31343142149192 + 30 +0.0 + 10 +14.95875191593985 + 20 +-24.31343142149192 + 30 +0.0 + 10 +14.95156312201529 + 20 +-24.32062021541651 + 30 +0.0 + 10 +14.95156312201529 + 20 +-24.32062021541651 + 30 +0.0 + 10 +14.95156312201529 + 20 +-24.32062021541651 + 30 +0.0 + 10 +14.94437432809069 + 20 +-24.32780900934111 + 30 +0.0 + 10 +14.94437432809069 + 20 +-24.32780900934111 + 30 +0.0 + 10 +14.94437432809069 + 20 +-24.32780900934111 + 30 +0.0 + 10 +14.93718553416609 + 20 +-24.3349978032657 + 30 +0.0 + 10 +14.93718553416609 + 20 +-24.3349978032657 + 30 +0.0 + 10 +14.93718553416609 + 20 +-24.3349978032657 + 30 +0.0 + 10 +14.91562024924833 + 20 +-24.35296869122117 + 30 +0.0 + 10 +14.91562024924833 + 20 +-24.35296869122117 + 30 +0.0 + 10 +14.9012437582551 + 20 +-24.3637507852521 + 30 +0.0 + 10 +14.89046056736821 + 20 +-24.37812837310126 + 30 +0.0 + 10 +14.87608407637501 + 20 +-24.39250486409449 + 30 +0.0 + 10 +14.77544754256658 + 20 +-24.50033019182749 + 30 +0.0 + 10 +14.71794048173782 + 20 +-24.64050289850921 + 30 +0.0 + 10 +14.71434608477549 + 20 +-24.78786439911548 + 30 +0.0 + 10 +14.71434608477549 + 20 +-24.85974795093749 + 30 +0.0 + 10 +14.72512817880642 + 20 +-24.9352258997218 + 30 +0.0 + 10 +14.75028786068654 + 20 +-25.00351505458148 + 30 +0.0 + 10 +14.75388225764878 + 20 +-25.02148594253698 + 30 +0.0 + 10 +14.76466435167971 + 20 +-25.0394568304925 + 30 +0.0 + 10 +14.77185314560431 + 20 +-25.05383332148573 + 30 +0.0 + 10 +14.77185314560431 + 20 +-25.05383332148573 + 30 +0.0 + 10 +14.78263523963523 + 20 +-25.07899300336579 + 30 +0.0 + 10 +14.78263523963523 + 20 +-25.07899300336579 + 30 +0.0 + 10 +14.78263523963523 + 20 +-25.07899300336579 + 30 +0.0 + 10 +14.79701173062843 + 20 +-25.10774598535221 + 30 +0.0 + 10 +14.79701173062843 + 20 +-25.10774598535221 + 30 +0.0 + 10 +14.79701173062843 + 20 +-25.10774598535221 + 30 +0.0 + 10 +14.81138822162166 + 20 +-25.13290566723228 + 30 +0.0 + 10 +14.81138822162166 + 20 +-25.13290566723228 + 30 +0.0 + 10 +14.8185770155462 + 20 +-25.14368776126321 + 30 +0.0 + 10 +14.8185770155462 + 20 +-25.14368776126321 + 30 +0.0 + 10 +14.82576471261488 + 20 +-25.1508765551878 + 30 +0.0 + 10 +14.82576471261488 + 20 +-25.1508765551878 + 30 +0.0 + 10 +14.8365468066458 + 20 +-25.16884744314327 + 30 +0.0 + 10 +14.8365468066458 + 20 +-25.16884744314327 + 30 +0.0 + 10 +14.8365468066458 + 20 +-25.16884744314327 + 30 +0.0 + 10 +14.84014120360805 + 20 +-25.17244184010557 + 30 +0.0 + 10 +14.84014120360805 + 20 +-25.17244184010557 + 30 +0.0 + 10 +14.84014120360805 + 20 +-25.17244184010557 + 30 +0.0 + 10 +14.84014120360805 + 20 +-25.17603623706787 + 30 +0.0 + 10 +14.84014120360805 + 20 +-25.17603623706787 + 30 +0.0 + 10 +14.84014120360805 + 20 +-25.17603623706787 + 30 +0.0 + 10 +14.84732999753265 + 20 +-25.18322503099244 + 30 +0.0 + 10 +14.84732999753265 + 20 +-25.18322503099244 + 30 +0.0 + 10 +14.84732999753265 + 20 +-25.18322503099244 + 30 +0.0 + 10 +14.85451879145724 + 20 +-25.19041382491703 + 30 +0.0 + 10 +14.85451879145724 + 20 +-25.19041382491703 + 30 +0.0 + 10 +14.85451879145724 + 20 +-25.19041382491703 + 30 +0.0 + 10 +15.06298065299868 + 20 +-25.45278835032498 + 30 +0.0 + 10 +15.06298065299868 + 20 +-25.45278835032498 + 30 +0.0 + 10 +15.06298065299868 + 20 +-25.45278835032498 + 30 +0.0 + 10 +15.47990547293744 + 20 +-25.97394410103445 + 30 +0.0 + 10 +15.47990547293744 + 20 +-25.97394410103445 + 30 +0.0 + 10 +15.47990547293744 + 20 +-25.97394410103445 + 30 +0.0 + 10 +15.90042468983855 + 20 +-26.49509985174399 + 30 +0.0 + 10 +15.90042468983855 + 20 +-26.49509985174399 + 30 +0.0 + 10 +15.90042468983855 + 20 +-26.49509985174399 + 30 +0.0 + 10 +16.00465562060925 + 20 +-26.62449046439479 + 30 +0.0 + 10 +16.00465562060925 + 20 +-26.62449046439479 + 30 +0.0 + 10 +16.00465562060925 + 20 +-26.62449046439479 + 30 +0.0 + 10 +16.05856828447574 + 20 +-26.68918522229223 + 30 +0.0 + 10 +16.05856828447574 + 20 +-26.68918522229223 + 30 +0.0 + 10 +16.08013356939356 + 20 +-26.71793820427865 + 30 +0.0 + 10 +16.10529215441769 + 20 +-26.74309788615872 + 30 +0.0 + 10 +16.13404623326005 + 20 +-26.76825756803878 + 30 +0.0 + 10 +16.19155329408884 + 20 +-26.81857583494303 + 30 +0.0 + 10 +16.25624805198625 + 20 +-26.85811200781632 + 30 +0.0 + 10 +16.32453720684599 + 20 +-26.88327168969641 + 30 +0.0 + 10 +16.36047898275696 + 20 +-26.89764818068958 + 30 +0.0 + 10 +16.396420758668 + 20 +-26.90483697461417 + 30 +0.0 + 10 +16.43236253457897 + 20 +-26.9120246716828 + 30 +0.0 + 10 +16.45033342253449 + 20 +-26.9156190686451 + 30 +0.0 + 10 +16.46830431049001 + 20 +-26.9192134656074 + 30 +0.0 + 10 +16.48986959540778 + 20 +-26.9192134656074 + 30 +0.0 + 10 +16.48986959540778 + 20 +-26.9192134656074 + 30 +0.0 + 10 +16.50424608640098 + 20 +-26.9192134656074 + 30 +0.0 + 10 +16.50424608640098 + 20 +-26.9192134656074 + 30 +0.0 + 10 +16.5078404833633 + 20 +-26.9192134656074 + 30 +0.0 + 10 +16.51502818043191 + 20 +-26.9192134656074 + 30 +0.0 + 10 +16.5186225773942 + 20 +-26.9192134656074 + 30 +0.0 + 10 +16.5186225773942 + 20 +-26.9192134656074 + 30 +0.0 + 10 +16.54018786231202 + 20 +-26.9192134656074 + 30 +0.0 + 10 +16.54018786231202 + 20 +-26.9192134656074 + 30 +0.0 + 10 +16.54018786231202 + 20 +-26.9192134656074 + 30 +0.0 + 10 +16.54378225927427 + 20 +-26.9192134656074 + 30 +0.0 + 10 +16.54378225927427 + 20 +-26.9192134656074 + 30 +0.0 + 10 +16.64801319004497 + 20 +-26.9192134656074 + 30 +0.0 + 10 +16.755838517778 + 20 +-26.890460483621 + 30 +0.0 + 10 +16.84569295755548 + 20 +-26.84014111986079 + 30 +0.0 + 10 +16.84569295755548 + 20 +-26.84014111986079 + 30 +0.0 + 10 +17.05056151899058 + 20 +-26.72153369809686 + 30 +0.0 + 10 +17.05056151899058 + 20 +-26.72153369809686 + 30 +0.0 + 10 +17.05056151899058 + 20 +-26.72153369809686 + 30 +0.0 + 10 +17.4567031480425 + 20 +-26.48431775771306 + 30 +0.0 + 10 +17.4567031480425 + 20 +-26.48431775771306 + 30 +0.0 + 10 +17.4567031480425 + 20 +-26.48431775771306 + 30 +0.0 + 10 +18.27258189996463 + 20 +-26.00988587694547 + 30 +0.0 + 10 +18.27258189996463 + 20 +-26.00988587694547 + 30 +0.0 + 10 +18.27258189996463 + 20 +-26.00988587694547 + 30 +0.0 + 10 +19.90074390999062 + 20 +-25.06102321226624 + 30 +0.0 + 10 +19.90074390999062 + 20 +-25.06102321226624 + 30 +0.0 + 10 +19.90074390999062 + 20 +-25.06102321226624 + 30 +0.0 + 10 +20.00497484076135 + 20 +-24.99992175447515 + 30 +0.0 + 10 +20.00497484076135 + 20 +-24.99992175447515 + 30 +0.0 + 10 +20.00497484076135 + 20 +-24.99992175447515 + 30 +0.0 + 10 +19.97622185877491 + 20 +-27.67039526592018 + 30 +0.0 + 10 +19.97622185877491 + 20 +-27.67039526592018 + 30 +0.0 + 10 +19.9043383069529 + 20 +-27.67039526592018 + 30 +0.0 + 10 +19.83245475513089 + 20 +-27.67039526592018 + 30 +0.0 + 10 +19.76057120330887 + 20 +-27.67039526592018 + 30 +0.0 + 10 +19.76057120330887 + 20 +-27.67039526592018 + 30 +0.0 + 10 +19.39037135016795 + 20 +-27.67398966288248 + 30 +0.0 + 10 +19.39037135016795 + 20 +-27.67398966288248 + 30 +0.0 + 10 +19.39037135016795 + 20 +-27.67398966288248 + 30 +0.0 + 10 +18.64637615006786 + 20 +-27.68117845680707 + 30 +0.0 + 10 +18.64637615006786 + 20 +-27.68117845680707 + 30 +0.0 + 10 +18.64637615006786 + 20 +-27.68117845680707 + 30 +0.0 + 10 +17.16198124368589 + 20 +-27.69555494780025 + 30 +0.0 + 10 +17.16198124368589 + 20 +-27.69555494780025 + 30 +0.0 + 10 +17.16198124368589 + 20 +-27.69555494780025 + 30 +0.0 + 10 +14.29023356776808 + 20 +-27.72790232674894 + 30 +0.0 + 10 +14.29023356776808 + 20 +-27.72790232674894 + 30 +0.0 + 10 +14.29742236169265 + 20 +-27.27863012786146 + 30 +0.0 + 10 +14.30101566179898 + 20 +-26.82935792897395 + 30 +0.0 + 10 +14.30820445572357 + 20 +-26.38008573008642 + 30 +0.0 + 10 +14.30820445572357 + 20 +-26.38008573008642 + 30 +0.0 + 10 +14.3189865497545 + 20 +-24.93163259961547 + 30 +0.0 + 10 +14.3189865497545 + 20 +-24.93163259961547 + 30 +0.0 + 10 +14.3189865497545 + 20 +-24.93163259961547 + 30 +0.0 + 10 +14.3261753436791 + 20 +-24.20920268443312 + 30 +0.0 + 10 +14.3261753436791 + 20 +-24.20920268443312 + 30 +0.0 + 10 +14.3261753436791 + 20 +-24.20920268443312 + 30 +0.0 + 10 +14.3261753436791 + 20 +-23.48317837228852 + 30 +0.0 + 10 +14.3261753436791 + 20 +-23.48317837228852 + 30 +0.0 + 10 +14.3261753436791 + 20 +-23.48317837228852 + 30 +0.0 + 10 +14.32976974064134 + 20 +-22.17489794849924 + 30 +0.0 + 10 +14.32976974064134 + 20 +-22.17489794849924 + 30 +0.0 + 10 +14.32976974064134 + 20 +-22.17489794849924 + 30 +0.0 + 10 +14.9335911372038 + 20 +-22.17489794849924 + 30 +0.0 + 10 +14.9335911372038 + 20 +-22.17489794849924 + 30 +0.0 + 10 +14.9335911372038 + 20 +-22.17489794849924 + 30 +0.0 + 10 +15.67758633730394 + 20 +-22.17130355153697 + 30 +0.0 + 10 +15.67758633730394 + 20 +-22.17130355153697 + 30 +0.0 + 10 +15.67758633730394 + 20 +-22.17130355153697 + 30 +0.0 + 10 +17.16198124368589 + 20 +-22.16770915457469 + 30 +0.0 + 10 +17.16198124368589 + 20 +-22.16770915457469 + 30 +0.0 + 10 +17.16198124368589 + 20 +-22.16770915457469 + 30 +0.0 + 10 +18.64637615006786 + 20 +-22.15333266358147 + 30 +0.0 + 10 +18.64637615006786 + 20 +-22.15333266358147 + 30 +0.0 + 10 +18.64637615006786 + 20 +-22.15333266358147 + 30 +0.0 + 10 +19.39037135016795 + 20 +-22.14255056955054 + 30 +0.0 + 10 +19.39037135016795 + 20 +-22.14255056955054 + 30 +0.0 + 10 +19.60602200563398 + 20 +-22.13895617258825 + 30 +0.0 + 10 +19.81807826413771 + 20 +-22.13536177562595 + 30 +0.0 + 10 +20.03372891960372 + 20 +-22.13176847551962 + 30 +0.0 + 10 +20.03372891960372 + 20 +-22.13176847551962 + 30 +0.0 + 10 +20.01935242861049 + 20 +-23.59100479687749 + 30 +0.0 + 10 +20.01935242861049 + 20 +-23.59100479687749 + 30 +0.0 + 10 +20.01935242861049 + 20 +-23.59100479687749 + 30 +0.0 + 10 +19.84683234298008 + 20 +-23.6916413306859 + 30 +0.0 + 10 +19.84683234298008 + 20 +-23.6916413306859 + 30 +0.0 + 10 +19.84683234298008 + 20 +-23.6916413306859 + 30 +0.0 + 10 +18.22945242698501 + 20 +-24.65128499254009 + 30 +0.0 + 10 +18.22945242698501 + 20 +-24.65128499254009 + 30 +0.0 + 0 +HATCH + 5 +47 +330 +41 +100 +AcDbEntity + 8 +Layer 1 + 62 + 250 +420 + 2301728 +100 +AcDbHatch + 10 +0.0 + 20 +0.0 + 30 +0.0 +210 +0.0 +220 +0.0 +230 +1.0 + 2 +SOLID + 70 + 1 + 71 + 0 + 91 + 2 + 92 + 1 + 93 + 1 + 72 + 4 + 94 + 3 + 73 + 0 + 74 + 1 + 95 + 281 + 96 + 277 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +1.0 + 40 +1.0 + 40 +1.0 + 40 +2.0 + 40 +2.0 + 40 +2.0 + 40 +3.0 + 40 +3.0 + 40 +3.0 + 40 +4.0 + 40 +4.0 + 40 +4.0 + 40 +5.0 + 40 +5.0 + 40 +5.0 + 40 +6.0 + 40 +6.0 + 40 +6.0 + 40 +7.0 + 40 +7.0 + 40 +7.0 + 40 +8.0 + 40 +8.0 + 40 +8.0 + 40 +9.0 + 40 +9.0 + 40 +9.0 + 40 +10.0 + 40 +10.0 + 40 +10.0 + 40 +11.0 + 40 +11.0 + 40 +11.0 + 40 +12.0 + 40 +12.0 + 40 +12.0 + 40 +13.0 + 40 +13.0 + 40 +13.0 + 40 +14.0 + 40 +14.0 + 40 +14.0 + 40 +15.0 + 40 +15.0 + 40 +15.0 + 40 +16.0 + 40 +16.0 + 40 +16.0 + 40 +17.0 + 40 +17.0 + 40 +17.0 + 40 +18.0 + 40 +18.0 + 40 +18.0 + 40 +19.0 + 40 +19.0 + 40 +19.0 + 40 +20.0 + 40 +20.0 + 40 +20.0 + 40 +21.0 + 40 +21.0 + 40 +21.0 + 40 +22.0 + 40 +22.0 + 40 +22.0 + 40 +23.0 + 40 +23.0 + 40 +23.0 + 40 +24.0 + 40 +24.0 + 40 +24.0 + 40 +25.0 + 40 +25.0 + 40 +25.0 + 40 +26.0 + 40 +26.0 + 40 +26.0 + 40 +27.0 + 40 +27.0 + 40 +27.0 + 40 +28.0 + 40 +28.0 + 40 +28.0 + 40 +29.0 + 40 +29.0 + 40 +29.0 + 40 +30.0 + 40 +30.0 + 40 +30.0 + 40 +31.0 + 40 +31.0 + 40 +31.0 + 40 +32.0 + 40 +32.0 + 40 +32.0 + 40 +33.0 + 40 +33.0 + 40 +33.0 + 40 +34.0 + 40 +34.0 + 40 +34.0 + 40 +35.0 + 40 +35.0 + 40 +35.0 + 40 +36.0 + 40 +36.0 + 40 +36.0 + 40 +37.0 + 40 +37.0 + 40 +37.0 + 40 +38.0 + 40 +38.0 + 40 +38.0 + 40 +39.0 + 40 +39.0 + 40 +39.0 + 40 +40.0 + 40 +40.0 + 40 +40.0 + 40 +41.0 + 40 +41.0 + 40 +41.0 + 40 +42.0 + 40 +42.0 + 40 +42.0 + 40 +43.0 + 40 +43.0 + 40 +43.0 + 40 +44.0 + 40 +44.0 + 40 +44.0 + 40 +45.0 + 40 +45.0 + 40 +45.0 + 40 +46.0 + 40 +46.0 + 40 +46.0 + 40 +47.0 + 40 +47.0 + 40 +47.0 + 40 +48.0 + 40 +48.0 + 40 +48.0 + 40 +49.0 + 40 +49.0 + 40 +49.0 + 40 +50.0 + 40 +50.0 + 40 +50.0 + 40 +51.0 + 40 +51.0 + 40 +51.0 + 40 +52.0 + 40 +52.0 + 40 +52.0 + 40 +53.0 + 40 +53.0 + 40 +53.0 + 40 +54.0 + 40 +54.0 + 40 +54.0 + 40 +55.0 + 40 +55.0 + 40 +55.0 + 40 +56.0 + 40 +56.0 + 40 +56.0 + 40 +57.0 + 40 +57.0 + 40 +57.0 + 40 +58.0 + 40 +58.0 + 40 +58.0 + 40 +59.0 + 40 +59.0 + 40 +59.0 + 40 +60.0 + 40 +60.0 + 40 +60.0 + 40 +61.0 + 40 +61.0 + 40 +61.0 + 40 +62.0 + 40 +62.0 + 40 +62.0 + 40 +63.0 + 40 +63.0 + 40 +63.0 + 40 +64.0 + 40 +64.0 + 40 +64.0 + 40 +65.0 + 40 +65.0 + 40 +65.0 + 40 +66.0 + 40 +66.0 + 40 +66.0 + 40 +67.0 + 40 +67.0 + 40 +67.0 + 40 +68.0 + 40 +68.0 + 40 +68.0 + 40 +69.0 + 40 +69.0 + 40 +69.0 + 40 +70.0 + 40 +70.0 + 40 +70.0 + 40 +71.0 + 40 +71.0 + 40 +71.0 + 40 +72.0 + 40 +72.0 + 40 +72.0 + 40 +73.0 + 40 +73.0 + 40 +73.0 + 40 +74.0 + 40 +74.0 + 40 +74.0 + 40 +75.0 + 40 +75.0 + 40 +75.0 + 40 +76.0 + 40 +76.0 + 40 +76.0 + 40 +77.0 + 40 +77.0 + 40 +77.0 + 40 +78.0 + 40 +78.0 + 40 +78.0 + 40 +79.0 + 40 +79.0 + 40 +79.0 + 40 +80.0 + 40 +80.0 + 40 +80.0 + 40 +81.0 + 40 +81.0 + 40 +81.0 + 40 +82.0 + 40 +82.0 + 40 +82.0 + 40 +83.0 + 40 +83.0 + 40 +83.0 + 40 +84.0 + 40 +84.0 + 40 +84.0 + 40 +85.0 + 40 +85.0 + 40 +85.0 + 40 +86.0 + 40 +86.0 + 40 +86.0 + 40 +87.0 + 40 +87.0 + 40 +87.0 + 40 +88.0 + 40 +88.0 + 40 +88.0 + 40 +89.0 + 40 +89.0 + 40 +89.0 + 40 +90.0 + 40 +90.0 + 40 +90.0 + 40 +91.0 + 40 +91.0 + 40 +91.0 + 40 +92.0 + 40 +92.0 + 40 +92.0 + 40 +92.0 + 10 +23.07799777800786 + 20 +-22.63135674759943 + 10 +23.07799777800786 + 20 +-22.63135674759943 + 10 +23.06002689005239 + 20 +-22.59182057472617 + 10 +23.06002689005239 + 20 +-22.59182057472617 + 10 +23.06002689005239 + 20 +-22.59182057472617 + 10 +23.05643358994606 + 20 +-22.58103848069524 + 10 +23.05643358994606 + 20 +-22.58103848069524 + 10 +23.05643358994606 + 20 +-22.58103848069524 + 10 +23.05284028983973 + 20 +-22.57384968677065 + 10 +23.05284028983973 + 20 +-22.57384968677065 + 10 +23.05284028983973 + 20 +-22.57384968677065 + 10 +23.04565149591518 + 20 +-22.55947319577743 + 10 +23.04565149591518 + 20 +-22.55947319577743 + 10 +23.04565149591518 + 20 +-22.55947319577743 + 10 +23.03846270199059 + 20 +-22.54509670478425 + 10 +23.03846270199059 + 20 +-22.54509670478425 + 10 +23.03846270199059 + 20 +-22.54509670478425 + 10 +23.03486940188425 + 20 +-22.53790791085965 + 10 +23.03486940188425 + 20 +-22.53790791085965 + 10 +23.03486940188425 + 20 +-22.53790791085965 + 10 +23.03486940188425 + 20 +-22.53431351389736 + 10 +23.03486940188425 + 20 +-22.53431351389736 + 10 +23.02768060795966 + 20 +-22.51993702290418 + 10 +23.03127610177792 + 20 +-22.52712471997281 + 10 +23.02768060795966 + 20 +-22.52353141986643 + 10 +23.02768060795966 + 20 +-22.52353141986643 + 10 +23.00611641989781 + 20 +-22.49118404091771 + 10 +23.00611641989781 + 20 +-22.49118404091771 + 10 +22.99173883204864 + 20 +-22.46961875599994 + 10 +22.97376794409317 + 20 +-22.45164786804445 + 10 +22.95939254995591 + 20 +-22.43008258312663 + 10 +22.94142166200037 + 20 +-22.41211169517116 + 10 +22.92345077404484 + 20 +-22.39414080721563 + 10 +22.90547988608937 + 20 +-22.37616991926016 + 10 +22.75093068841448 + 20 +-22.23959160954074 + 10 +22.52449793891754 + 20 +-22.19286664274285 + 10 +22.33041147151337 + 20 +-22.25396810053391 + 10 +22.30525178963325 + 20 +-22.26115689445851 + 10 +22.27649880764688 + 20 +-22.27193898848943 + 10 +22.25133912576676 + 20 +-22.28272108252036 + 10 +22.25133912576676 + 20 +-22.28272108252036 + 10 +22.2261794438867 + 20 +-22.29709757351356 + 10 +22.2261794438867 + 20 +-22.29709757351356 + 10 +22.2261794438867 + 20 +-22.29709757351356 + 10 +22.21180185603756 + 20 +-22.30428636743816 + 10 +22.21180185603756 + 20 +-22.30428636743816 + 10 +22.21180185603756 + 20 +-22.30428636743816 + 10 +22.20101976200661 + 20 +-22.31147516136275 + 10 +22.20101976200661 + 20 +-22.31147516136275 + 10 +22.20101976200661 + 20 +-22.31147516136275 + 10 +22.19383096808201 + 20 +-22.315069558325 + 10 +22.19383096808201 + 20 +-22.315069558325 + 10 +22.19383096808201 + 20 +-22.315069558325 + 10 +22.0931944342736 + 20 +-22.37617101611613 + 10 +22.0931944342736 + 20 +-22.37617101611613 + 10 +22.0931944342736 + 20 +-22.37617101611613 + 10 +21.88832587283853 + 20 +-22.49477843788001 + 10 +21.88832587283853 + 20 +-22.49477843788001 + 10 +21.88832587283853 + 20 +-22.49477843788001 + 10 +21.48218424378658 + 20 +-22.73199437826381 + 10 +21.48218424378658 + 20 +-22.73199437826381 + 10 +21.48218424378658 + 20 +-22.73199437826381 + 10 +21.20183883042316 + 20 +-22.89373236986335 + 10 +21.20183883042316 + 20 +-22.89373236986335 + 10 +21.20183883042316 + 20 +-22.72839998130151 + 10 +21.20183883042316 + 20 +-22.55947429263339 + 10 +21.20543213052949 + 20 +-22.3905475071093 + 10 +21.20543213052949 + 20 +-22.28991097330089 + 10 +21.20543213052949 + 20 +-22.19286773959882 + 10 +21.20543213052949 + 20 +-22.09223120579039 + 10 +21.20543213052949 + 20 +-22.04191293888617 + 10 +21.20543213052949 + 20 +-21.99159467198198 + 10 +21.20543213052949 + 20 +-21.94127530822177 + 10 +21.20543213052949 + 20 +-21.91252232623538 + 10 +21.20183883042316 + 20 +-21.88376824739301 + 10 +21.20183883042316 + 20 +-21.85501526540659 + 10 +21.20183883042316 + 20 +-21.82626228342019 + 10 +21.19465003649859 + 20 +-21.79750820457778 + 10 +21.19105673639223 + 20 +-21.76875522259136 + 10 +21.15152166037488 + 20 +-21.53513367916985 + 10 +21.02212995086814 + 20 +-21.31588862674158 + 10 +20.82804458031993 + 20 +-21.16852767456324 + 10 +20.73100134661786 + 20 +-21.09304972577898 + 10 +20.62317601888486 + 20 +-21.03913706191245 + 10 +20.5045685971209 + 20 +-21.00319528600145 + 10 +20.4758156151345 + 20 +-20.99600704050484 + 10 +20.44706153629214 + 20 +-20.98522439804595 + 10 +20.41471415734342 + 20 +-20.98163000108368 + 10 +20.38236677839473 + 20 +-20.97803560412138 + 10 +20.34642500248371 + 20 +-20.97084735862477 + 10 +20.31767092364134 + 20 +-20.96725351009043 + 10 +20.31767092364134 + 20 +-20.96725351009043 + 10 +20.23859857789474 + 20 +-20.96365911312813 + 10 +20.23859857789474 + 20 +-20.96365911312813 + 10 +20.23859857789474 + 20 +-20.96365911312813 + 10 +20.15233853507955 + 20 +-20.96365911312813 + 10 +20.15233853507955 + 20 +-20.96365911312813 + 10 +19.94028227657582 + 20 +-20.96365911312813 + 10 +19.72822601807211 + 20 +-20.96725351009043 + 10 +19.51257536260608 + 20 +-20.96725351009043 + 10 +18.64997274074209 + 20 +-20.97084790705273 + 10 +17.75861713689164 + 20 +-20.98522439804595 + 10 +16.85288394519201 + 20 +-20.99241264354254 + 10 +15.94715075349237 + 20 +-21.00319528600145 + 10 +15.02704216765551 + 20 +-21.00678913453577 + 10 +14.1069324849627 + 20 +-21.00678913453577 + 10 +13.84815125966108 + 20 +-21.00319473757347 + 10 +13.59296552817775 + 20 +-21.11820885923104 + 10 +13.42044434569137 + 20 +-21.30510543585468 + 10 +13.33418430287619 + 20 +-21.39855427259448 + 10 +13.26589514801645 + 20 +-21.50997399728979 + 10 +13.22276457818089 + 20 +-21.63217581601599 + 10 +13.20119929326309 + 20 +-21.69327727380707 + 10 +13.18682280226984 + 20 +-21.75437763474222 + 10 +13.17604070823894 + 20 +-21.82266678960193 + 10 +13.17604070823894 + 20 +-21.82985558352652 + 10 +13.17244631127664 + 20 +-21.8406376775574 + 10 +13.17244631127664 + 20 +-21.84782647148199 + 10 +13.17244631127664 + 20 +-21.84782647148199 + 10 +13.17244631127664 + 20 +-21.86939175639979 + 10 +13.17244631127664 + 20 +-21.86939175639979 + 10 +13.17244631127664 + 20 +-21.86939175639979 + 10 +13.16885191431435 + 20 +-21.91252232623538 + 10 +13.16885191431435 + 20 +-21.91252232623538 + 10 +13.16885191431435 + 20 +-21.91252232623538 + 10 +13.16885191431435 + 20 +-21.9233044202663 + 10 +13.16885191431435 + 20 +-21.9233044202663 + 10 +13.16885191431435 + 20 +-21.9233044202663 + 10 +13.16885191431435 + 20 +-21.93768091125952 + 10 +13.16885191431435 + 20 +-21.93768091125952 + 10 +13.16885191431435 + 20 +-21.93768091125952 + 10 +13.16885191431435 + 20 +-22.00237566915694 + 10 +13.16885191431435 + 20 +-22.00237566915694 + 10 +13.16885191431435 + 20 +-22.46242996207535 + 10 +13.1616631203898 + 20 +-22.91529655792515 + 10 +13.15806982028347 + 20 +-23.37175755073728 + 10 +13.14728772625254 + 20 +-24.27749074243689 + 10 +13.14009893232795 + 20 +-25.16884634628731 + 10 +13.12931683829702 + 20 +-26.03144896815135 + 10 +13.12572244133475 + 20 +-26.46275027908339 + 10 +13.12212804437243 + 20 +-26.88686279609081 + 10 +13.1185347442661 + 20 +-27.30378761602962 + 10 +13.11494034730382 + 20 +-27.51224947757105 + 10 +13.11494034730382 + 20 +-27.71711803900613 + 10 +13.11494034730382 + 20 +-27.92198660044121 + 10 +13.11494034730382 + 20 +-28.04059402220517 + 10 +13.14010002918386 + 20 +-28.16279693778733 + 10 +13.18322950216351 + 20 +-28.27421556562667 + 10 +13.2263600719991 + 20 +-28.38563529032191 + 10 +13.29105482989656 + 20 +-28.4862718241304 + 10 +13.37012717564312 + 20 +-28.57253186694558 + 10 +13.44919952138967 + 20 +-28.65879190976082 + 10 +13.54264726127354 + 20 +-28.7306754615828 + 10 +13.64328489193791 + 20 +-28.78458812544929 + 10 +13.64328489193791 + 20 +-28.78458812544929 + 10 +13.72235723768452 + 20 +-28.82052990136031 + 10 +13.72235723768452 + 20 +-28.82052990136031 + 10 +13.75111021967091 + 20 +-28.83131199539124 + 10 +13.776269901551 + 20 +-28.83850078931583 + 10 +13.80502288353745 + 20 +-28.84928288334671 + 10 +13.85893554740394 + 20 +-28.86365937433993 + 10 +13.91284821127043 + 20 +-28.87803586533315 + 10 +13.97754296916784 + 20 +-28.8852246592577 + 10 +13.99191946016107 + 20 +-28.88881905622 + 10 +14.00989034811654 + 20 +-28.88881905622 + 10 +14.02067353900343 + 20 +-28.89241345318229 + 10 +14.02067353900343 + 20 +-28.89241345318229 + 10 +14.05661531491442 + 20 +-28.89241345318229 + 10 +14.05661531491442 + 20 +-28.89241345318229 + 10 +14.05661531491442 + 20 +-28.89241345318229 + 10 +14.09255709082544 + 20 +-28.89241345318229 + 10 +14.09255709082544 + 20 +-28.89241345318229 + 10 +14.09255709082544 + 20 +-28.89241345318229 + 10 +14.10333918485632 + 20 +-28.89241345318229 + 10 +14.10333918485632 + 20 +-28.89241345318229 + 10 +14.12131007281184 + 20 +-28.89241345318229 + 10 +14.11412127888724 + 20 +-28.89241345318229 + 10 +14.12131007281184 + 20 +-28.89241345318229 + 10 +14.12131007281184 + 20 +-28.89241345318229 + 10 +14.13928096076736 + 20 +-28.89241345318229 + 10 +14.13928096076736 + 20 +-28.89241345318229 + 10 +14.18600483070925 + 20 +-28.89241345318229 + 10 +14.23272979750714 + 20 +-28.89241345318229 + 10 +14.28304806441138 + 20 +-28.89241345318229 + 10 +14.37649690115116 + 20 +-28.89241345318229 + 10 +14.4735390379973 + 20 +-28.89241345318229 + 10 +14.5633934777748 + 20 +-28.88881905622 + 10 +14.93718772787803 + 20 +-28.8852246592577 + 10 +15.29660548698803 + 20 +-28.8816302622954 + 10 +15.64164675510483 + 20 +-28.87803696218907 + 10 +16.33172929133843 + 20 +-28.87084816826453 + 10 +16.96430366988731 + 20 +-28.8636604711959 + 10 +17.52499559347012 + 20 +-28.8564716772713 + 10 +18.08568751705287 + 20 +-28.84928288334671 + 10 +18.57449588881375 + 20 +-28.84568958324038 + 10 +18.97704312090337 + 20 +-28.84568958324038 + 10 +19.7821386819386 + 20 +-28.84209518627808 + 10 +20.242194071713 + 20 +-28.83850078931583 + 10 +20.242194071713 + 20 +-28.83850078931583 + 10 +20.242194071713 + 20 +-28.83850078931583 + 10 +20.27454145066172 + 20 +-28.83850078931583 + 10 +20.33564290845278 + 20 +-28.83490639235353 + 10 +20.39674436624387 + 20 +-28.82412429832261 + 10 +20.49019210612773 + 20 +-28.81334110743571 + 10 +20.60161183082303 + 20 +-28.76661723749382 + 10 +20.7130315555183 + 20 +-28.71989336755188 + 10 +20.84242216816907 + 20 +-28.637226624843 + 10 +20.95384079600843 + 20 +-28.50424271208587 + 10 +21.00775345987495 + 20 +-28.43595355722616 + 10 +21.06166612374144 + 20 +-28.35688121147955 + 10 +21.10120229661475 + 20 +-28.26702677170207 + 10 +21.10839109053935 + 20 +-28.24186708982198 + 10 +21.11917318457027 + 20 +-28.22030290176013 + 10 +21.12636197849482 + 20 +-28.19514321988006 + 10 +21.13355077241936 + 20 +-28.169983538 + 10 +21.14073956634396 + 20 +-28.14482495297587 + 10 +21.14792616655662 + 20 +-28.11966527109576 + 10 +21.15511496048121 + 20 +-28.09450558921569 + 10 +21.15870826058755 + 20 +-28.06575260722926 + 10 +21.16230375440581 + 20 +-28.03699962524287 + 10 +21.16589705451214 + 20 +-28.02262313424964 + 10 +21.16589705451214 + 20 +-28.00824664325647 + 10 +21.1694925483304 + 20 +-27.99386905540728 + 10 +21.1694925483304 + 20 +-27.98308696137635 + 10 +21.1694925483304 + 20 +-27.96870937352719 + 10 +21.1694925483304 + 20 +-27.95792727949626 + 10 +21.1694925483304 + 20 +-27.94714518546533 + 10 +21.1694925483304 + 20 +-27.93276759761619 + 10 +21.17308584843673 + 20 +-27.91839110662297 + 10 +21.17308584843673 + 20 +-27.91839110662297 + 10 +21.17308584843673 + 20 +-27.90401461562977 + 10 +21.17308584843673 + 20 +-27.90401461562977 + 10 +21.17308584843673 + 20 +-27.90401461562977 + 10 +21.17308584843673 + 20 +-27.87526163364338 + 10 +21.17308584843673 + 20 +-27.87526163364338 + 10 +21.17308584843673 + 20 +-27.85010195176326 + 10 +21.17308584843673 + 20 +-27.82494336673913 + 10 +21.17308584843673 + 20 +-27.79618928789677 + 10 +21.17308584843673 + 20 +-27.74227662403028 + 10 +21.17308584843673 + 20 +-27.68836396016377 + 10 +21.17308584843673 + 20 +-27.63445129629728 + 10 +21.17308584843673 + 20 +-27.52303157160198 + 10 +21.17308584843673 + 20 +-27.40801854680032 + 10 +21.17308584843673 + 20 +-27.28581563121816 + 10 +21.17308584843673 + 20 +-27.04141199376575 + 10 +21.17667914854307 + 20 +-26.77903637150186 + 10 +21.17667914854307 + 20 +-26.49869095813844 + 10 +21.18027244864942 + 20 +-25.93799903455561 + 10 +21.18746124257402 + 20 +-25.30542355915082 + 10 +21.19105673639223 + 20 +-24.61534211977318 + 10 +21.19105673639223 + 20 +-24.51111118900245 + 10 +21.19465003649859 + 20 +-24.40328586126945 + 10 +21.19465003649859 + 20 +-24.29546053353645 + 10 +21.19465003649859 + 20 +-24.29546053353645 + 10 +21.53609690765312 + 20 +-24.09778076602593 + 10 +21.53609690765312 + 20 +-24.09778076602593 + 10 +21.53609690765312 + 20 +-24.09778076602593 + 10 +22.35197565957522 + 20 +-23.62334888525834 + 10 +22.35197565957522 + 20 +-23.62334888525834 + 10 +22.35197565957522 + 20 +-23.62334888525834 + 10 +22.75811728862714 + 20 +-23.38613294487454 + 10 +22.75811728862714 + 20 +-23.38613294487454 + 10 +22.75811728862714 + 20 +-23.38613294487454 + 10 +22.8084366523873 + 20 +-23.35737996288809 + 10 +22.8084366523873 + 20 +-23.35737996288809 + 10 +22.82640754034282 + 20 +-23.34659786885721 + 10 +22.85156722222286 + 20 +-23.32862698090169 + 10 +22.87313141028471 + 20 +-23.31424939305253 + 10 +22.91266648630209 + 20 +-23.28190201410383 + 10 +22.95220375603132 + 20 +-23.24596023819281 + 10 +22.98455003812404 + 20 +-23.20642406531952 + 10 +23.04924479602151 + 20 +-23.12735171957292 + 10 +23.09237536585705 + 20 +-23.02671518576451 + 10 +23.11034625381257 + 20 +-22.92607865195608 + 10 +23.1211283478435 + 20 +-22.83263091207224 + 10 +23.1139395539189 + 20 +-22.72839998130151 + 10 +23.07799777800786 + 20 +-22.63135674759943 + 97 + 0 + 97 + 0 + 92 + 1 + 93 + 1 + 72 + 4 + 94 + 3 + 73 + 0 + 74 + 1 + 95 + 212 + 96 + 208 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +1.0 + 40 +1.0 + 40 +1.0 + 40 +2.0 + 40 +2.0 + 40 +2.0 + 40 +3.0 + 40 +3.0 + 40 +3.0 + 40 +4.0 + 40 +4.0 + 40 +4.0 + 40 +5.0 + 40 +5.0 + 40 +5.0 + 40 +6.0 + 40 +6.0 + 40 +6.0 + 40 +7.0 + 40 +7.0 + 40 +7.0 + 40 +8.0 + 40 +8.0 + 40 +8.0 + 40 +9.0 + 40 +9.0 + 40 +9.0 + 40 +10.0 + 40 +10.0 + 40 +10.0 + 40 +11.0 + 40 +11.0 + 40 +11.0 + 40 +12.0 + 40 +12.0 + 40 +12.0 + 40 +13.0 + 40 +13.0 + 40 +13.0 + 40 +14.0 + 40 +14.0 + 40 +14.0 + 40 +15.0 + 40 +15.0 + 40 +15.0 + 40 +16.0 + 40 +16.0 + 40 +16.0 + 40 +17.0 + 40 +17.0 + 40 +17.0 + 40 +18.0 + 40 +18.0 + 40 +18.0 + 40 +19.0 + 40 +19.0 + 40 +19.0 + 40 +20.0 + 40 +20.0 + 40 +20.0 + 40 +21.0 + 40 +21.0 + 40 +21.0 + 40 +22.0 + 40 +22.0 + 40 +22.0 + 40 +23.0 + 40 +23.0 + 40 +23.0 + 40 +24.0 + 40 +24.0 + 40 +24.0 + 40 +25.0 + 40 +25.0 + 40 +25.0 + 40 +26.0 + 40 +26.0 + 40 +26.0 + 40 +27.0 + 40 +27.0 + 40 +27.0 + 40 +28.0 + 40 +28.0 + 40 +28.0 + 40 +29.0 + 40 +29.0 + 40 +29.0 + 40 +30.0 + 40 +30.0 + 40 +30.0 + 40 +31.0 + 40 +31.0 + 40 +31.0 + 40 +32.0 + 40 +32.0 + 40 +32.0 + 40 +33.0 + 40 +33.0 + 40 +33.0 + 40 +34.0 + 40 +34.0 + 40 +34.0 + 40 +35.0 + 40 +35.0 + 40 +35.0 + 40 +36.0 + 40 +36.0 + 40 +36.0 + 40 +37.0 + 40 +37.0 + 40 +37.0 + 40 +38.0 + 40 +38.0 + 40 +38.0 + 40 +39.0 + 40 +39.0 + 40 +39.0 + 40 +40.0 + 40 +40.0 + 40 +40.0 + 40 +41.0 + 40 +41.0 + 40 +41.0 + 40 +42.0 + 40 +42.0 + 40 +42.0 + 40 +43.0 + 40 +43.0 + 40 +43.0 + 40 +44.0 + 40 +44.0 + 40 +44.0 + 40 +45.0 + 40 +45.0 + 40 +45.0 + 40 +46.0 + 40 +46.0 + 40 +46.0 + 40 +47.0 + 40 +47.0 + 40 +47.0 + 40 +48.0 + 40 +48.0 + 40 +48.0 + 40 +49.0 + 40 +49.0 + 40 +49.0 + 40 +50.0 + 40 +50.0 + 40 +50.0 + 40 +51.0 + 40 +51.0 + 40 +51.0 + 40 +52.0 + 40 +52.0 + 40 +52.0 + 40 +53.0 + 40 +53.0 + 40 +53.0 + 40 +54.0 + 40 +54.0 + 40 +54.0 + 40 +55.0 + 40 +55.0 + 40 +55.0 + 40 +56.0 + 40 +56.0 + 40 +56.0 + 40 +57.0 + 40 +57.0 + 40 +57.0 + 40 +58.0 + 40 +58.0 + 40 +58.0 + 40 +59.0 + 40 +59.0 + 40 +59.0 + 40 +60.0 + 40 +60.0 + 40 +60.0 + 40 +61.0 + 40 +61.0 + 40 +61.0 + 40 +62.0 + 40 +62.0 + 40 +62.0 + 40 +63.0 + 40 +63.0 + 40 +63.0 + 40 +64.0 + 40 +64.0 + 40 +64.0 + 40 +65.0 + 40 +65.0 + 40 +65.0 + 40 +66.0 + 40 +66.0 + 40 +66.0 + 40 +67.0 + 40 +67.0 + 40 +67.0 + 40 +68.0 + 40 +68.0 + 40 +68.0 + 40 +69.0 + 40 +69.0 + 40 +69.0 + 40 +69.0 + 10 +18.22945242698501 + 20 +-24.65128499254009 + 10 +18.22945242698501 + 20 +-24.65128499254009 + 10 +16.68036166584966 + 20 +-25.56780027827065 + 10 +16.68036166584966 + 20 +-25.56780027827065 + 10 +16.56175424408573 + 20 +-25.41325108059573 + 10 +16.44314572546586 + 20 +-25.25870078606486 + 10 +16.32453830370193 + 20 +-25.10774598535221 + 10 +16.32453830370193 + 20 +-25.10774598535221 + 10 +15.91480227768774 + 20 +-24.57580704375583 + 10 +15.91480227768774 + 20 +-24.57580704375583 + 10 +15.91480227768774 + 20 +-24.57580704375583 + 10 +15.81057134691703 + 20 +-24.44641643110501 + 10 +15.81057134691703 + 20 +-24.44641643110501 + 10 +15.79978925288611 + 20 +-24.43563433707411 + 10 +15.79619485592381 + 20 +-24.42485114618722 + 10 +15.78181836493059 + 20 +-24.41047465519404 + 10 +15.77103627089966 + 20 +-24.39609816420082 + 10 +15.76025308001279 + 20 +-24.38172167320759 + 10 +15.74587658901957 + 20 +-24.37093848232073 + 10 +15.74587658901957 + 20 +-24.37093848232073 + 10 +15.7063404161463 + 20 +-24.33499670640973 + 10 +15.7063404161463 + 20 +-24.33499670640973 + 10 +15.69196392515308 + 20 +-24.32421461237881 + 10 +15.67758743415986 + 20 +-24.31343142149192 + 10 +15.66320984631066 + 20 +-24.30264932746104 + 10 +15.54460242454679 + 20 +-24.21998368160808 + 10 +15.39724092394052 + 20 +-24.18044750873479 + 10 +15.25706821725875 + 20 +-24.19841839669031 + 10 +15.18518466543674 + 20 +-24.20560719061485 + 10 +15.11330111361475 + 20 +-24.22717137867673 + 10 +15.04860635571734 + 20 +-24.25951985448139 + 10 +15.03063546776184 + 20 +-24.27030194851232 + 10 +15.01266457980637 + 20 +-24.27749074243689 + 10 +14.99469369185085 + 20 +-24.28827283646782 + 10 +14.98391159781992 + 20 +-24.29546163039241 + 10 +14.98031720085767 + 20 +-24.29905493049874 + 10 +14.97672280389533 + 20 +-24.30264932746104 + 10 +14.97672280389533 + 20 +-24.30264932746104 + 10 +14.95875191593985 + 20 +-24.31343142149192 + 10 +14.95875191593985 + 20 +-24.31343142149192 + 10 +14.95875191593985 + 20 +-24.31343142149192 + 10 +14.95156312201529 + 20 +-24.32062021541651 + 10 +14.95156312201529 + 20 +-24.32062021541651 + 10 +14.95156312201529 + 20 +-24.32062021541651 + 10 +14.94437432809069 + 20 +-24.32780900934111 + 10 +14.94437432809069 + 20 +-24.32780900934111 + 10 +14.94437432809069 + 20 +-24.32780900934111 + 10 +14.93718553416609 + 20 +-24.3349978032657 + 10 +14.93718553416609 + 20 +-24.3349978032657 + 10 +14.93718553416609 + 20 +-24.3349978032657 + 10 +14.91562024924833 + 20 +-24.35296869122117 + 10 +14.91562024924833 + 20 +-24.35296869122117 + 10 +14.9012437582551 + 20 +-24.3637507852521 + 10 +14.89046056736821 + 20 +-24.37812837310126 + 10 +14.87608407637501 + 20 +-24.39250486409449 + 10 +14.77544754256658 + 20 +-24.50033019182749 + 10 +14.71794048173782 + 20 +-24.64050289850921 + 10 +14.71434608477549 + 20 +-24.78786439911548 + 10 +14.71434608477549 + 20 +-24.85974795093749 + 10 +14.72512817880642 + 20 +-24.9352258997218 + 10 +14.75028786068654 + 20 +-25.00351505458148 + 10 +14.75388225764878 + 20 +-25.02148594253698 + 10 +14.76466435167971 + 20 +-25.0394568304925 + 10 +14.77185314560431 + 20 +-25.05383332148573 + 10 +14.77185314560431 + 20 +-25.05383332148573 + 10 +14.78263523963523 + 20 +-25.07899300336579 + 10 +14.78263523963523 + 20 +-25.07899300336579 + 10 +14.78263523963523 + 20 +-25.07899300336579 + 10 +14.79701173062843 + 20 +-25.10774598535221 + 10 +14.79701173062843 + 20 +-25.10774598535221 + 10 +14.79701173062843 + 20 +-25.10774598535221 + 10 +14.81138822162166 + 20 +-25.13290566723228 + 10 +14.81138822162166 + 20 +-25.13290566723228 + 10 +14.8185770155462 + 20 +-25.14368776126321 + 10 +14.8185770155462 + 20 +-25.14368776126321 + 10 +14.82576471261488 + 20 +-25.1508765551878 + 10 +14.82576471261488 + 20 +-25.1508765551878 + 10 +14.8365468066458 + 20 +-25.16884744314327 + 10 +14.8365468066458 + 20 +-25.16884744314327 + 10 +14.8365468066458 + 20 +-25.16884744314327 + 10 +14.84014120360805 + 20 +-25.17244184010557 + 10 +14.84014120360805 + 20 +-25.17244184010557 + 10 +14.84014120360805 + 20 +-25.17244184010557 + 10 +14.84014120360805 + 20 +-25.17603623706787 + 10 +14.84014120360805 + 20 +-25.17603623706787 + 10 +14.84014120360805 + 20 +-25.17603623706787 + 10 +14.84732999753265 + 20 +-25.18322503099244 + 10 +14.84732999753265 + 20 +-25.18322503099244 + 10 +14.84732999753265 + 20 +-25.18322503099244 + 10 +14.85451879145724 + 20 +-25.19041382491703 + 10 +14.85451879145724 + 20 +-25.19041382491703 + 10 +14.85451879145724 + 20 +-25.19041382491703 + 10 +15.06298065299868 + 20 +-25.45278835032498 + 10 +15.06298065299868 + 20 +-25.45278835032498 + 10 +15.06298065299868 + 20 +-25.45278835032498 + 10 +15.47990547293744 + 20 +-25.97394410103445 + 10 +15.47990547293744 + 20 +-25.97394410103445 + 10 +15.47990547293744 + 20 +-25.97394410103445 + 10 +15.90042468983855 + 20 +-26.49509985174399 + 10 +15.90042468983855 + 20 +-26.49509985174399 + 10 +15.90042468983855 + 20 +-26.49509985174399 + 10 +16.00465562060925 + 20 +-26.62449046439479 + 10 +16.00465562060925 + 20 +-26.62449046439479 + 10 +16.00465562060925 + 20 +-26.62449046439479 + 10 +16.05856828447574 + 20 +-26.68918522229223 + 10 +16.05856828447574 + 20 +-26.68918522229223 + 10 +16.08013356939356 + 20 +-26.71793820427865 + 10 +16.10529215441769 + 20 +-26.74309788615872 + 10 +16.13404623326005 + 20 +-26.76825756803878 + 10 +16.19155329408884 + 20 +-26.81857583494303 + 10 +16.25624805198625 + 20 +-26.85811200781632 + 10 +16.32453720684599 + 20 +-26.88327168969641 + 10 +16.36047898275696 + 20 +-26.89764818068958 + 10 +16.396420758668 + 20 +-26.90483697461417 + 10 +16.43236253457897 + 20 +-26.9120246716828 + 10 +16.45033342253449 + 20 +-26.9156190686451 + 10 +16.46830431049001 + 20 +-26.9192134656074 + 10 +16.48986959540778 + 20 +-26.9192134656074 + 10 +16.48986959540778 + 20 +-26.9192134656074 + 10 +16.50424608640098 + 20 +-26.9192134656074 + 10 +16.50424608640098 + 20 +-26.9192134656074 + 10 +16.5078404833633 + 20 +-26.9192134656074 + 10 +16.51502818043191 + 20 +-26.9192134656074 + 10 +16.5186225773942 + 20 +-26.9192134656074 + 10 +16.5186225773942 + 20 +-26.9192134656074 + 10 +16.54018786231202 + 20 +-26.9192134656074 + 10 +16.54018786231202 + 20 +-26.9192134656074 + 10 +16.54018786231202 + 20 +-26.9192134656074 + 10 +16.54378225927427 + 20 +-26.9192134656074 + 10 +16.54378225927427 + 20 +-26.9192134656074 + 10 +16.64801319004497 + 20 +-26.9192134656074 + 10 +16.755838517778 + 20 +-26.890460483621 + 10 +16.84569295755548 + 20 +-26.84014111986079 + 10 +16.84569295755548 + 20 +-26.84014111986079 + 10 +17.05056151899058 + 20 +-26.72153369809686 + 10 +17.05056151899058 + 20 +-26.72153369809686 + 10 +17.05056151899058 + 20 +-26.72153369809686 + 10 +17.4567031480425 + 20 +-26.48431775771306 + 10 +17.4567031480425 + 20 +-26.48431775771306 + 10 +17.4567031480425 + 20 +-26.48431775771306 + 10 +18.27258189996463 + 20 +-26.00988587694547 + 10 +18.27258189996463 + 20 +-26.00988587694547 + 10 +18.27258189996463 + 20 +-26.00988587694547 + 10 +19.90074390999062 + 20 +-25.06102321226624 + 10 +19.90074390999062 + 20 +-25.06102321226624 + 10 +19.90074390999062 + 20 +-25.06102321226624 + 10 +20.00497484076135 + 20 +-24.99992175447515 + 10 +20.00497484076135 + 20 +-24.99992175447515 + 10 +20.00497484076135 + 20 +-24.99992175447515 + 10 +19.97622185877491 + 20 +-27.67039526592018 + 10 +19.97622185877491 + 20 +-27.67039526592018 + 10 +19.9043383069529 + 20 +-27.67039526592018 + 10 +19.83245475513089 + 20 +-27.67039526592018 + 10 +19.76057120330887 + 20 +-27.67039526592018 + 10 +19.76057120330887 + 20 +-27.67039526592018 + 10 +19.39037135016795 + 20 +-27.67398966288248 + 10 +19.39037135016795 + 20 +-27.67398966288248 + 10 +19.39037135016795 + 20 +-27.67398966288248 + 10 +18.64637615006786 + 20 +-27.68117845680707 + 10 +18.64637615006786 + 20 +-27.68117845680707 + 10 +18.64637615006786 + 20 +-27.68117845680707 + 10 +17.16198124368589 + 20 +-27.69555494780025 + 10 +17.16198124368589 + 20 +-27.69555494780025 + 10 +17.16198124368589 + 20 +-27.69555494780025 + 10 +14.29023356776808 + 20 +-27.72790232674894 + 10 +14.29023356776808 + 20 +-27.72790232674894 + 10 +14.29742236169265 + 20 +-27.27863012786146 + 10 +14.30101566179898 + 20 +-26.82935792897395 + 10 +14.30820445572357 + 20 +-26.38008573008642 + 10 +14.30820445572357 + 20 +-26.38008573008642 + 10 +14.3189865497545 + 20 +-24.93163259961547 + 10 +14.3189865497545 + 20 +-24.93163259961547 + 10 +14.3189865497545 + 20 +-24.93163259961547 + 10 +14.3261753436791 + 20 +-24.20920268443312 + 10 +14.3261753436791 + 20 +-24.20920268443312 + 10 +14.3261753436791 + 20 +-24.20920268443312 + 10 +14.3261753436791 + 20 +-23.48317837228852 + 10 +14.3261753436791 + 20 +-23.48317837228852 + 10 +14.3261753436791 + 20 +-23.48317837228852 + 10 +14.32976974064134 + 20 +-22.17489794849924 + 10 +14.32976974064134 + 20 +-22.17489794849924 + 10 +14.32976974064134 + 20 +-22.17489794849924 + 10 +14.9335911372038 + 20 +-22.17489794849924 + 10 +14.9335911372038 + 20 +-22.17489794849924 + 10 +14.9335911372038 + 20 +-22.17489794849924 + 10 +15.67758633730394 + 20 +-22.17130355153697 + 10 +15.67758633730394 + 20 +-22.17130355153697 + 10 +15.67758633730394 + 20 +-22.17130355153697 + 10 +17.16198124368589 + 20 +-22.16770915457469 + 10 +17.16198124368589 + 20 +-22.16770915457469 + 10 +17.16198124368589 + 20 +-22.16770915457469 + 10 +18.64637615006786 + 20 +-22.15333266358147 + 10 +18.64637615006786 + 20 +-22.15333266358147 + 10 +18.64637615006786 + 20 +-22.15333266358147 + 10 +19.39037135016795 + 20 +-22.14255056955054 + 10 +19.39037135016795 + 20 +-22.14255056955054 + 10 +19.60602200563398 + 20 +-22.13895617258825 + 10 +19.81807826413771 + 20 +-22.13536177562595 + 10 +20.03372891960372 + 20 +-22.13176847551962 + 10 +20.03372891960372 + 20 +-22.13176847551962 + 10 +20.01935242861049 + 20 +-23.59100479687749 + 10 +20.01935242861049 + 20 +-23.59100479687749 + 10 +20.01935242861049 + 20 +-23.59100479687749 + 10 +19.84683234298008 + 20 +-23.6916413306859 + 10 +19.84683234298008 + 20 +-23.6916413306859 + 10 +19.84683234298008 + 20 +-23.6916413306859 + 10 +18.22945242698501 + 20 +-24.65128499254009 + 10 +18.22945242698501 + 20 +-24.65128499254009 + 97 + 0 + 97 + 0 + 75 + 2 + 76 + 1 + 98 + 0 + 0 +SPLINE + 5 +48 +330 +41 +100 +AcDbEntity + 8 +Layer 1 +370 + 0 +100 +AcDbSpline +210 +0.0 +220 +0.0 +230 +1.0 + 70 + 11 + 71 + 3 + 72 + 284 + 73 + 280 + 74 + 0 + 42 +0.0000000001 + 43 +0.0000000001 + 12 +1.0 + 22 +0.0 + 32 +0.0 + 13 +1.0 + 23 +0.0 + 33 +0.0 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +1.0 + 40 +1.0 + 40 +1.0 + 40 +2.0 + 40 +2.0 + 40 +2.0 + 40 +3.0 + 40 +3.0 + 40 +3.0 + 40 +4.0 + 40 +4.0 + 40 +4.0 + 40 +5.0 + 40 +5.0 + 40 +5.0 + 40 +6.0 + 40 +6.0 + 40 +6.0 + 40 +7.0 + 40 +7.0 + 40 +7.0 + 40 +8.0 + 40 +8.0 + 40 +8.0 + 40 +9.0 + 40 +9.0 + 40 +9.0 + 40 +10.0 + 40 +10.0 + 40 +10.0 + 40 +11.0 + 40 +11.0 + 40 +11.0 + 40 +12.0 + 40 +12.0 + 40 +12.0 + 40 +13.0 + 40 +13.0 + 40 +13.0 + 40 +14.0 + 40 +14.0 + 40 +14.0 + 40 +15.0 + 40 +15.0 + 40 +15.0 + 40 +16.0 + 40 +16.0 + 40 +16.0 + 40 +17.0 + 40 +17.0 + 40 +17.0 + 40 +18.0 + 40 +18.0 + 40 +18.0 + 40 +19.0 + 40 +19.0 + 40 +19.0 + 40 +20.0 + 40 +20.0 + 40 +20.0 + 40 +21.0 + 40 +21.0 + 40 +21.0 + 40 +22.0 + 40 +22.0 + 40 +22.0 + 40 +23.0 + 40 +23.0 + 40 +23.0 + 40 +24.0 + 40 +24.0 + 40 +24.0 + 40 +25.0 + 40 +25.0 + 40 +25.0 + 40 +26.0 + 40 +26.0 + 40 +26.0 + 40 +27.0 + 40 +27.0 + 40 +27.0 + 40 +28.0 + 40 +28.0 + 40 +28.0 + 40 +29.0 + 40 +29.0 + 40 +29.0 + 40 +30.0 + 40 +30.0 + 40 +30.0 + 40 +31.0 + 40 +31.0 + 40 +31.0 + 40 +32.0 + 40 +32.0 + 40 +32.0 + 40 +33.0 + 40 +33.0 + 40 +33.0 + 40 +34.0 + 40 +34.0 + 40 +34.0 + 40 +35.0 + 40 +35.0 + 40 +35.0 + 40 +36.0 + 40 +36.0 + 40 +36.0 + 40 +37.0 + 40 +37.0 + 40 +37.0 + 40 +38.0 + 40 +38.0 + 40 +38.0 + 40 +39.0 + 40 +39.0 + 40 +39.0 + 40 +40.0 + 40 +40.0 + 40 +40.0 + 40 +41.0 + 40 +41.0 + 40 +41.0 + 40 +42.0 + 40 +42.0 + 40 +42.0 + 40 +43.0 + 40 +43.0 + 40 +43.0 + 40 +44.0 + 40 +44.0 + 40 +44.0 + 40 +45.0 + 40 +45.0 + 40 +45.0 + 40 +46.0 + 40 +46.0 + 40 +46.0 + 40 +47.0 + 40 +47.0 + 40 +47.0 + 40 +48.0 + 40 +48.0 + 40 +48.0 + 40 +49.0 + 40 +49.0 + 40 +49.0 + 40 +50.0 + 40 +50.0 + 40 +50.0 + 40 +51.0 + 40 +51.0 + 40 +51.0 + 40 +52.0 + 40 +52.0 + 40 +52.0 + 40 +53.0 + 40 +53.0 + 40 +53.0 + 40 +54.0 + 40 +54.0 + 40 +54.0 + 40 +55.0 + 40 +55.0 + 40 +55.0 + 40 +56.0 + 40 +56.0 + 40 +56.0 + 40 +57.0 + 40 +57.0 + 40 +57.0 + 40 +58.0 + 40 +58.0 + 40 +58.0 + 40 +59.0 + 40 +59.0 + 40 +59.0 + 40 +60.0 + 40 +60.0 + 40 +60.0 + 40 +61.0 + 40 +61.0 + 40 +61.0 + 40 +62.0 + 40 +62.0 + 40 +62.0 + 40 +63.0 + 40 +63.0 + 40 +63.0 + 40 +64.0 + 40 +64.0 + 40 +64.0 + 40 +65.0 + 40 +65.0 + 40 +65.0 + 40 +66.0 + 40 +66.0 + 40 +66.0 + 40 +67.0 + 40 +67.0 + 40 +67.0 + 40 +68.0 + 40 +68.0 + 40 +68.0 + 40 +69.0 + 40 +69.0 + 40 +69.0 + 40 +70.0 + 40 +70.0 + 40 +70.0 + 40 +71.0 + 40 +71.0 + 40 +71.0 + 40 +72.0 + 40 +72.0 + 40 +72.0 + 40 +73.0 + 40 +73.0 + 40 +73.0 + 40 +74.0 + 40 +74.0 + 40 +74.0 + 40 +75.0 + 40 +75.0 + 40 +75.0 + 40 +76.0 + 40 +76.0 + 40 +76.0 + 40 +77.0 + 40 +77.0 + 40 +77.0 + 40 +78.0 + 40 +78.0 + 40 +78.0 + 40 +79.0 + 40 +79.0 + 40 +79.0 + 40 +80.0 + 40 +80.0 + 40 +80.0 + 40 +81.0 + 40 +81.0 + 40 +81.0 + 40 +82.0 + 40 +82.0 + 40 +82.0 + 40 +83.0 + 40 +83.0 + 40 +83.0 + 40 +84.0 + 40 +84.0 + 40 +84.0 + 40 +85.0 + 40 +85.0 + 40 +85.0 + 40 +86.0 + 40 +86.0 + 40 +86.0 + 40 +87.0 + 40 +87.0 + 40 +87.0 + 40 +88.0 + 40 +88.0 + 40 +88.0 + 40 +89.0 + 40 +89.0 + 40 +89.0 + 40 +90.0 + 40 +90.0 + 40 +90.0 + 40 +91.0 + 40 +91.0 + 40 +91.0 + 40 +92.0 + 40 +92.0 + 40 +92.0 + 40 +93.0 + 40 +93.0 + 40 +93.0 + 40 +93.0 + 10 +20.96821509328973 + 20 +-27.88604482453022 + 30 +0.0 + 10 +20.96821509328973 + 20 +-27.86088514265015 + 30 +0.0 + 10 +20.96821509328973 + 20 +-27.835726557626 + 30 +0.0 + 10 +20.96821509328973 + 20 +-27.80697247878361 + 30 +0.0 + 10 +20.96821509328973 + 20 +-27.75305981491712 + 30 +0.0 + 10 +20.96821509328973 + 20 +-27.69914715105066 + 30 +0.0 + 10 +20.96821509328973 + 20 +-27.64523448718412 + 30 +0.0 + 10 +20.96821509328973 + 20 +-27.53381476248887 + 30 +0.0 + 10 +20.96821509328973 + 20 +-27.41880173768721 + 30 +0.0 + 10 +20.96821509328973 + 20 +-27.29659882210505 + 30 +0.0 + 10 +20.96821509328973 + 20 +-27.05219518465259 + 30 +0.0 + 10 +20.96462069632748 + 20 +-26.7898195623887 + 30 +0.0 + 10 +20.96102629936513 + 20 +-26.50947414902528 + 30 +0.0 + 10 +20.95743190240288 + 20 +-25.9487822254425 + 30 +0.0 + 10 +20.95024420533425 + 20 +-25.31620675003766 + 30 +0.0 + 10 +20.94664980837196 + 20 +-24.62612531066002 + 30 +0.0 + 10 +20.94664980837196 + 20 +-24.56861824983124 + 30 +0.0 + 10 +20.94664980837196 + 20 +-24.51111118900245 + 30 +0.0 + 10 +20.94664980837196 + 20 +-24.45360522502961 + 30 +0.0 + 10 +20.94664980837196 + 20 +-24.45360522502961 + 30 +0.0 + 10 +20.27094485998752 + 20 +-24.84896476005062 + 30 +0.0 + 10 +20.27094485998752 + 20 +-24.84896476005062 + 30 +0.0 + 10 +20.27094485998752 + 20 +-24.84896476005062 + 30 +0.0 + 10 +20.30329223893624 + 20 +-27.8249444635951 + 30 +0.0 + 10 +20.30329223893624 + 20 +-27.8249444635951 + 30 +0.0 + 10 +20.30329223893624 + 20 +-27.91120450641029 + 30 +0.0 + 10 +20.23500308407647 + 20 +-27.98308805823232 + 30 +0.0 + 10 +20.14874304126129 + 20 +-27.98308805823232 + 30 +0.0 + 10 +20.14874304126129 + 20 +-27.98308805823232 + 30 +0.0 + 10 +20.14874304126129 + 20 +-27.98308805823232 + 30 +0.0 + 10 +20.14874304126129 + 20 +-27.98308805823232 + 30 +0.0 + 10 +20.14874304126129 + 20 +-27.98308805823232 + 30 +0.0 + 10 +20.14514864429899 + 20 +-27.98308805823232 + 30 +0.0 + 10 +20.14514864429899 + 20 +-27.98308805823232 + 30 +0.0 + 10 +20.02294682557279 + 20 +-27.98308805823232 + 30 +0.0 + 10 +19.89715060988429 + 20 +-27.98308805823232 + 30 +0.0 + 10 +19.77494879115807 + 20 +-27.98308805823232 + 30 +0.0 + 10 +19.77494879115807 + 20 +-27.98308805823232 + 30 +0.0 + 10 +19.40474893801711 + 20 +-27.97949366127002 + 30 +0.0 + 10 +19.40474893801711 + 20 +-27.97949366127002 + 30 +0.0 + 10 +19.40474893801711 + 20 +-27.97949366127002 + 30 +0.0 + 10 +18.66075373791702 + 20 +-27.97230486734545 + 30 +0.0 + 10 +18.66075373791702 + 20 +-27.97230486734545 + 30 +0.0 + 10 +18.66075373791702 + 20 +-27.97230486734545 + 30 +0.0 + 10 +14.20397352495284 + 20 +-27.92198660044121 + 30 +0.0 + 10 +14.20397352495284 + 20 +-27.92198660044121 + 30 +0.0 + 10 +14.15006086108636 + 20 +-27.92198660044121 + 30 +0.0 + 10 +14.10693029125082 + 20 +-27.87885603060562 + 30 +0.0 + 10 +14.10693029125082 + 20 +-27.82494336673913 + 30 +0.0 + 10 +14.10693029125082 + 20 +-27.82494336673913 + 30 +0.0 + 10 +14.10693029125082 + 20 +-27.82494336673913 + 30 +0.0 + 10 +14.10693029125082 + 20 +-27.82494336673913 + 30 +0.0 + 10 +14.09974149732622 + 20 +-27.34332378890291 + 30 +0.0 + 10 +14.09255380025754 + 20 +-26.85810981410441 + 30 +0.0 + 10 +14.0889594032953 + 20 +-26.37649023626818 + 30 +0.0 + 10 +14.0889594032953 + 20 +-26.37649023626818 + 30 +0.0 + 10 +14.07817730926435 + 20 +-24.9280371057972 + 30 +0.0 + 10 +14.07817730926435 + 20 +-24.9280371057972 + 30 +0.0 + 10 +14.07817730926435 + 20 +-24.9280371057972 + 30 +0.0 + 10 +14.07458291230205 + 20 +-24.20560719061485 + 30 +0.0 + 10 +14.07458291230205 + 20 +-24.20560719061485 + 30 +0.0 + 10 +14.07458291230205 + 20 +-24.20560719061485 + 30 +0.0 + 10 +14.07458291230205 + 20 +-23.48317727543256 + 30 +0.0 + 10 +14.07458291230205 + 20 +-23.48317727543256 + 30 +0.0 + 10 +14.07458291230205 + 20 +-23.48317727543256 + 30 +0.0 + 10 +14.07098851533978 + 20 +-22.0347241449616 + 30 +0.0 + 10 +14.07098851533978 + 20 +-22.0347241449616 + 30 +0.0 + 10 +14.07098851533978 + 20 +-21.95924619617729 + 30 +0.0 + 10 +14.13208997313083 + 20 +-21.89814583524215 + 30 +0.0 + 10 +14.20756682505917 + 20 +-21.8945514382799 + 30 +0.0 + 10 +14.20756682505917 + 20 +-21.8945514382799 + 30 +0.0 + 10 +14.20756682505917 + 20 +-21.8945514382799 + 30 +0.0 + 10 +14.20756682505917 + 20 +-21.8945514382799 + 30 +0.0 + 10 +14.20756682505917 + 20 +-21.8945514382799 + 30 +0.0 + 10 +14.95156202515929 + 20 +-21.8945514382799 + 30 +0.0 + 10 +14.95156202515929 + 20 +-21.8945514382799 + 30 +0.0 + 10 +14.95156202515929 + 20 +-21.8945514382799 + 30 +0.0 + 10 +15.69555722525944 + 20 +-21.89814583524215 + 30 +0.0 + 10 +15.69555722525944 + 20 +-21.89814583524215 + 30 +0.0 + 10 +15.69555722525944 + 20 +-21.89814583524215 + 30 +0.0 + 10 +17.17995213164141 + 20 +-21.90174023220445 + 30 +0.0 + 10 +17.17995213164141 + 20 +-21.90174023220445 + 30 +0.0 + 10 +17.17995213164141 + 20 +-21.90174023220445 + 30 +0.0 + 10 +18.66434703802335 + 20 +-21.91611672319767 + 30 +0.0 + 10 +18.66434703802335 + 20 +-21.91611672319767 + 30 +0.0 + 10 +18.66434703802335 + 20 +-21.91611672319767 + 30 +0.0 + 10 +19.40834223812345 + 20 +-21.9268988172286 + 30 +0.0 + 10 +19.40834223812345 + 20 +-21.9268988172286 + 30 +0.0 + 10 +19.65634027253817 + 20 +-21.9304932141909 + 30 +0.0 + 10 +19.9043383069529 + 20 +-21.93408761115314 + 30 +0.0 + 10 +20.15233743822359 + 20 +-21.93768091125952 + 30 +0.0 + 10 +20.15233743822359 + 20 +-21.93768091125952 + 30 +0.0 + 10 +20.15233743822359 + 20 +-21.93768091125952 + 30 +0.0 + 10 +20.15233743822359 + 20 +-21.93768091125952 + 30 +0.0 + 10 +20.2062501020901 + 20 +-21.93768091125952 + 30 +0.0 + 10 +20.24938067192566 + 20 +-21.98081148109509 + 30 +0.0 + 10 +20.24938067192566 + 20 +-22.0347241449616 + 30 +0.0 + 10 +20.24938067192566 + 20 +-22.0347241449616 + 30 +0.0 + 10 +20.26375716291886 + 20 +-23.45801869040843 + 30 +0.0 + 10 +20.26375716291886 + 20 +-23.45801869040843 + 30 +0.0 + 10 +20.26375716291886 + 20 +-23.45801869040843 + 30 +0.0 + 10 +20.94665090522787 + 20 +-23.05906475842514 + 30 +0.0 + 10 +20.94665090522787 + 20 +-23.05906475842514 + 30 +0.0 + 10 +20.94665090522787 + 20 +-22.84341410295913 + 30 +0.0 + 10 +20.94665090522787 + 20 +-22.62057465356856 + 30 +0.0 + 10 +20.94305650826562 + 20 +-22.3977363010339 + 30 +0.0 + 10 +20.94305650826562 + 20 +-22.29709976722549 + 30 +0.0 + 10 +20.94305650826562 + 20 +-22.20005653352342 + 30 +0.0 + 10 +20.94305650826562 + 20 +-22.09941999971498 + 30 +0.0 + 10 +20.94305650826562 + 20 +-22.04910173281077 + 30 +0.0 + 10 +20.94305650826562 + 20 +-21.99878346590652 + 30 +0.0 + 10 +20.94305650826562 + 20 +-21.94846410214637 + 30 +0.0 + 10 +20.94305650826562 + 20 +-21.9233044202663 + 30 +0.0 + 10 +20.93946211130333 + 20 +-21.90533353231078 + 30 +0.0 + 10 +20.93946211130333 + 20 +-21.88376934424898 + 30 +0.0 + 10 +20.93946211130333 + 20 +-21.86220405933118 + 30 +0.0 + 10 +20.93227331737876 + 20 +-21.84063877441336 + 30 +0.0 + 10 +20.93227331737876 + 20 +-21.82266788645789 + 30 +0.0 + 10 +20.90352033539231 + 20 +-21.6573354978961 + 30 +0.0 + 10 +20.81007149865253 + 20 +-21.49919190325886 + 30 +0.0 + 10 +20.66989879197081 + 20 +-21.39496097248815 + 30 +0.0 + 10 +20.60160963711112 + 20 +-21.34104830862167 + 30 +0.0 + 10 +20.52253729136449 + 20 +-21.30151213574835 + 30 +0.0 + 10 +20.43987164551158 + 20 +-21.2763535507242 + 30 +0.0 + 10 +20.41830636059379 + 20 +-21.27275915376195 + 30 +0.0 + 10 +20.39674107567602 + 20 +-21.26557090826532 + 30 +0.0 + 10 +20.37877018772049 + 20 +-21.26197705973103 + 30 +0.0 + 10 +20.3572049028027 + 20 +-21.25838266276873 + 30 +0.0 + 10 +20.34282841180953 + 20 +-21.25478881423444 + 30 +0.0 + 10 +20.31766872992941 + 20 +-21.25119441727214 + 30 +0.0 + 10 +20.31766872992941 + 20 +-21.25119441727214 + 30 +0.0 + 10 +20.23859638418281 + 20 +-21.24760002030985 + 30 +0.0 + 10 +20.23859638418281 + 20 +-21.24760002030985 + 30 +0.0 + 10 +20.23859638418281 + 20 +-21.24760002030985 + 30 +0.0 + 10 +20.16671283236085 + 20 +-21.24760002030985 + 30 +0.0 + 10 +20.16671283236085 + 20 +-21.24760002030985 + 30 +0.0 + 10 +19.95465657385714 + 20 +-21.24760002030985 + 30 +0.0 + 10 +19.7426003153534 + 20 +-21.24400562334755 + 30 +0.0 + 10 +19.52694965988742 + 20 +-21.24400562334755 + 30 +0.0 + 10 +18.66434703802335 + 20 +-21.24041122638525 + 30 +0.0 + 10 +17.77299143417288 + 20 +-21.22603473539205 + 30 +0.0 + 10 +16.8672582424733 + 20 +-21.21884648989544 + 30 +0.0 + 10 +15.96152505077372 + 20 +-21.20806384743653 + 30 +0.0 + 10 +15.0414164649368 + 20 +-21.20446999890224 + 30 +0.0 + 10 +14.12130678224399 + 20 +-21.20446999890224 + 30 +0.0 + 10 +13.91643822080889 + 20 +-21.20446999890224 + 30 +0.0 + 10 +13.7187584532984 + 20 +-21.29432443867972 + 30 +0.0 + 10 +13.58218014357898 + 20 +-21.44528033624829 + 30 +0.0 + 10 +13.51389098871924 + 20 +-21.5207582850326 + 30 +0.0 + 10 +13.45997832485275 + 20 +-21.60701832784783 + 30 +0.0 + 10 +13.42763094590403 + 20 +-21.70406156154993 + 30 +0.0 + 10 +13.40966005794851 + 20 +-21.75078543149185 + 30 +0.0 + 10 +13.39887796391763 + 20 +-21.80110479525198 + 30 +0.0 + 10 +13.39528356695534 + 20 +-21.84782866519393 + 30 +0.0 + 10 +13.39528356695534 + 20 +-21.8550174591185 + 30 +0.0 + 10 +13.39168916999304 + 20 +-21.85861075922483 + 30 +0.0 + 10 +13.39168916999304 + 20 +-21.86579955314942 + 30 +0.0 + 10 +13.39168916999304 + 20 +-21.86579955314942 + 30 +0.0 + 10 +13.39168916999304 + 20 +-21.88736483806719 + 30 +0.0 + 10 +13.39168916999304 + 20 +-21.88736483806719 + 30 +0.0 + 10 +13.39168916999304 + 20 +-21.88736483806719 + 30 +0.0 + 10 +13.38809477303074 + 20 +-21.9412775019337 + 30 +0.0 + 10 +13.38809477303074 + 20 +-21.9412775019337 + 30 +0.0 + 10 +13.38809477303074 + 20 +-21.944871898896 + 30 +0.0 + 10 +13.38809477303074 + 20 +-21.944871898896 + 30 +0.0 + 10 +13.38809477303074 + 20 +-21.944871898896 + 30 +0.0 + 10 +13.38809477303074 + 20 +-21.944871898896 + 30 +0.0 + 10 +13.38809477303074 + 20 +-21.944871898896 + 30 +0.0 + 10 +13.38809477303074 + 20 +-21.944871898896 + 30 +0.0 + 10 +13.38809477303074 + 20 +-21.944871898896 + 30 +0.0 + 10 +13.38809477303074 + 20 +-21.9664371838138 + 30 +0.0 + 10 +13.38809477303074 + 20 +-21.9664371838138 + 30 +0.0 + 10 +13.38809477303074 + 20 +-21.9664371838138 + 30 +0.0 + 10 +13.38809477303074 + 20 +-22.00956775364938 + 30 +0.0 + 10 +13.38809477303074 + 20 +-22.00956775364938 + 30 +0.0 + 10 +13.38809477303074 + 20 +-22.46962204656779 + 30 +0.0 + 10 +13.39168916999304 + 20 +-22.92608303937989 + 30 +0.0 + 10 +13.39887686706167 + 20 +-23.37894963522972 + 30 +0.0 + 10 +13.40965896109259 + 20 +-24.28468282692933 + 30 +0.0 + 10 +13.41684775501719 + 20 +-25.17603843077978 + 30 +0.0 + 10 +13.42762984904812 + 20 +-26.0386410526438 + 30 +0.0 + 10 +13.43122424601036 + 20 +-26.46994236357583 + 30 +0.0 + 10 +13.43481864297266 + 20 +-26.89405488058325 + 30 +0.0 + 10 +13.43841194307902 + 20 +-27.31097970052206 + 30 +0.0 + 10 +13.44200634004132 + 20 +-27.51944156206349 + 30 +0.0 + 10 +13.44200634004132 + 20 +-27.72431012349858 + 30 +0.0 + 10 +13.44560073700356 + 20 +-27.92558428797138 + 30 +0.0 + 10 +13.44560073700356 + 20 +-28.09091667653317 + 30 +0.0 + 10 +13.51029549490103 + 20 +-28.24906027117039 + 30 +0.0 + 10 +13.61812082263403 + 20 +-28.37126208989659 + 30 +0.0 + 10 +13.67203348650052 + 20 +-28.43236354768768 + 30 +0.0 + 10 +13.73313494429158 + 20 +-28.48268181459192 + 30 +0.0 + 10 +13.80501849611359 + 20 +-28.52221798746521 + 30 +0.0 + 10 +13.80501849611359 + 20 +-28.52221798746521 + 30 +0.0 + 10 +13.85893115998008 + 20 +-28.54737766934527 + 30 +0.0 + 10 +13.85893115998008 + 20 +-28.54737766934527 + 30 +0.0 + 10 +13.85893115998008 + 20 +-28.54737766934527 + 30 +0.0 + 10 +13.91284382384659 + 20 +-28.56894295426306 + 30 +0.0 + 10 +13.91284382384659 + 20 +-28.56894295426306 + 30 +0.0 + 10 +13.94878559975761 + 20 +-28.57972504829399 + 30 +0.0 + 10 +13.9883217726309 + 20 +-28.59050823918088 + 30 +0.0 + 10 +14.02426354854192 + 20 +-28.59410263614313 + 30 +0.0 + 10 +14.03145234246646 + 20 +-28.59410263614313 + 30 +0.0 + 10 +14.04223443649739 + 20 +-28.59769703310543 + 30 +0.0 + 10 +14.04942323042198 + 20 +-28.59769703310543 + 30 +0.0 + 10 +14.04942323042198 + 20 +-28.59769703310543 + 30 +0.0 + 10 +14.085365006333 + 20 +-28.60129143006773 + 30 +0.0 + 10 +14.085365006333 + 20 +-28.60129143006773 + 30 +0.0 + 10 +14.085365006333 + 20 +-28.60129143006773 + 30 +0.0 + 10 +14.12130678224399 + 20 +-28.60488582703002 + 30 +0.0 + 10 +14.12130678224399 + 20 +-28.60488582703002 + 30 +0.0 + 10 +14.12130678224399 + 20 +-28.60488582703002 + 30 +0.0 + 10 +14.12849557616859 + 20 +-28.60488582703002 + 30 +0.0 + 10 +14.12849557616859 + 20 +-28.60488582703002 + 30 +0.0 + 10 +14.12849557616859 + 20 +-28.60488582703002 + 30 +0.0 + 10 +14.13208997313083 + 20 +-28.60488582703002 + 30 +0.0 + 10 +14.13208997313083 + 20 +-28.60488582703002 + 30 +0.0 + 10 +14.13208997313083 + 20 +-28.60488582703002 + 30 +0.0 + 10 +14.15006086108636 + 20 +-28.60488582703002 + 30 +0.0 + 10 +14.15006086108636 + 20 +-28.60488582703002 + 30 +0.0 + 10 +14.19678473102825 + 20 +-28.60488582703002 + 30 +0.0 + 10 +14.24350969782616 + 20 +-28.60488582703002 + 30 +0.0 + 10 +14.29382796473038 + 20 +-28.60488582703002 + 30 +0.0 + 10 +14.38727680147015 + 20 +-28.60488582703002 + 30 +0.0 + 10 +14.48072563820996 + 20 +-28.60848022399232 + 30 +0.0 + 10 +14.57417337809382 + 20 +-28.60848022399232 + 30 +0.0 + 10 +14.94796762819702 + 20 +-28.61207462095462 + 30 +0.0 + 10 +15.30738538730707 + 20 +-28.61566901791689 + 30 +0.0 + 10 +15.65242665542382 + 20 +-28.61926231802325 + 30 +0.0 + 10 +16.34250919165745 + 20 +-28.62645111194782 + 30 +0.0 + 10 +16.9750835702063 + 20 +-28.63363880901645 + 30 +0.0 + 10 +17.53577549378911 + 20 +-28.64082760294104 + 30 +0.0 + 10 +18.09646741737192 + 20 +-28.64801639686564 + 30 +0.0 + 10 +18.58527578913274 + 20 +-28.65160969697197 + 30 +0.0 + 10 +18.98782302122239 + 20 +-28.65160969697197 + 30 +0.0 + 10 +19.7929185822576 + 20 +-28.65520409393422 + 30 +0.0 + 10 +20.252973972032 + 20 +-28.65520409393422 + 30 +0.0 + 10 +20.252973972032 + 20 +-28.65520409393422 + 30 +0.0 + 10 +20.252973972032 + 20 +-28.65520409393422 + 30 +0.0 + 10 +20.27813365391211 + 20 +-28.65520409393422 + 30 +0.0 + 10 +20.3284519208163 + 20 +-28.65160969697197 + 30 +0.0 + 10 +20.37517579075822 + 20 +-28.64442090304738 + 30 +0.0 + 10 +20.45065373954251 + 20 +-28.63723320597875 + 30 +0.0 + 10 +20.53691378235774 + 20 +-28.59769703310543 + 30 +0.0 + 10 +20.62317382517297 + 20 +-28.56175525719444 + 30 +0.0 + 10 +20.72740475594368 + 20 +-28.49706049929702 + 30 +0.0 + 10 +20.81725919572116 + 20 +-28.39282847167033 + 30 +0.0 + 10 +20.86038976555675 + 20 +-28.33891580780386 + 30 +0.0 + 10 +20.89992484157404 + 20 +-28.27781435001278 + 30 +0.0 + 10 +20.93227331737876 + 20 +-28.20233749808444 + 30 +0.0 + 10 +20.93946211130333 + 20 +-28.18436661012892 + 30 +0.0 + 10 +20.94664980837196 + 20 +-28.16639572217344 + 30 +0.0 + 10 +20.95383860229655 + 20 +-28.14483043725565 + 30 +0.0 + 10 +20.96102739622114 + 20 +-28.12685954930013 + 30 +0.0 + 10 +20.96462069632748 + 20 +-28.10529426438236 + 30 +0.0 + 10 +20.97180949025202 + 20 +-28.08372897946454 + 30 +0.0 + 10 +20.97899828417662 + 20 +-28.06216369454675 + 30 +0.0 + 10 +20.97899828417662 + 20 +-28.04419280659128 + 30 +0.0 + 10 +20.98259158428295 + 20 +-28.02262752167346 + 30 +0.0 + 10 +20.98259158428295 + 20 +-28.01184542764253 + 30 +0.0 + 10 +20.98618598124525 + 20 +-28.00465663371799 + 30 +0.0 + 10 +20.98618598124525 + 20 +-27.99387453968706 + 30 +0.0 + 10 +20.98618598124525 + 20 +-27.98309244565613 + 30 +0.0 + 10 +20.98618598124525 + 20 +-27.96871485780697 + 30 +0.0 + 10 +20.98618598124525 + 20 +-27.95793276377604 + 30 +0.0 + 10 +20.98618598124525 + 20 +-27.94715066974512 + 30 +0.0 + 10 +20.98618598124525 + 20 +-27.93277308189598 + 30 +0.0 + 10 +20.98618598124525 + 20 +-27.91839659090275 + 30 +0.0 + 10 +20.98618598124525 + 20 +-27.91839659090275 + 30 +0.0 + 10 +20.98618598124525 + 20 +-27.91480219394046 + 30 +0.0 + 10 +20.98618598124525 + 20 +-27.91480219394046 + 30 +0.0 + 10 +20.98618598124525 + 20 +-27.91480219394046 + 30 +0.0 + 10 +20.98618598124525 + 20 +-27.91480219394046 + 30 +0.0 + 10 +20.98618598124525 + 20 +-27.91480219394046 + 30 +0.0 + 10 +20.98618598124525 + 20 +-27.91480219394046 + 30 +0.0 + 10 +20.98618598124525 + 20 +-27.90402009990956 + 30 +0.0 + 10 +20.98618598124525 + 20 +-27.90402009990956 + 30 +0.0 + 10 +20.98618598124525 + 20 +-27.90402009990956 + 30 +0.0 + 10 +20.96821509328973 + 20 +-27.88604482453022 + 30 +0.0 + 10 +20.96821509328973 + 20 +-27.88604482453022 + 30 +0.0 + 0 +HATCH + 5 +49 +330 +41 +100 +AcDbEntity + 8 +Layer 1 + 62 + 205 +420 + 8277387 +100 +AcDbHatch + 10 +0.0 + 20 +0.0 + 30 +0.0 +210 +0.0 +220 +0.0 +230 +1.0 + 2 +SOLID + 70 + 1 + 71 + 0 + 91 + 1 + 92 + 1 + 93 + 1 + 72 + 4 + 94 + 3 + 73 + 0 + 74 + 1 + 95 + 284 + 96 + 280 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +1.0 + 40 +1.0 + 40 +1.0 + 40 +2.0 + 40 +2.0 + 40 +2.0 + 40 +3.0 + 40 +3.0 + 40 +3.0 + 40 +4.0 + 40 +4.0 + 40 +4.0 + 40 +5.0 + 40 +5.0 + 40 +5.0 + 40 +6.0 + 40 +6.0 + 40 +6.0 + 40 +7.0 + 40 +7.0 + 40 +7.0 + 40 +8.0 + 40 +8.0 + 40 +8.0 + 40 +9.0 + 40 +9.0 + 40 +9.0 + 40 +10.0 + 40 +10.0 + 40 +10.0 + 40 +11.0 + 40 +11.0 + 40 +11.0 + 40 +12.0 + 40 +12.0 + 40 +12.0 + 40 +13.0 + 40 +13.0 + 40 +13.0 + 40 +14.0 + 40 +14.0 + 40 +14.0 + 40 +15.0 + 40 +15.0 + 40 +15.0 + 40 +16.0 + 40 +16.0 + 40 +16.0 + 40 +17.0 + 40 +17.0 + 40 +17.0 + 40 +18.0 + 40 +18.0 + 40 +18.0 + 40 +19.0 + 40 +19.0 + 40 +19.0 + 40 +20.0 + 40 +20.0 + 40 +20.0 + 40 +21.0 + 40 +21.0 + 40 +21.0 + 40 +22.0 + 40 +22.0 + 40 +22.0 + 40 +23.0 + 40 +23.0 + 40 +23.0 + 40 +24.0 + 40 +24.0 + 40 +24.0 + 40 +25.0 + 40 +25.0 + 40 +25.0 + 40 +26.0 + 40 +26.0 + 40 +26.0 + 40 +27.0 + 40 +27.0 + 40 +27.0 + 40 +28.0 + 40 +28.0 + 40 +28.0 + 40 +29.0 + 40 +29.0 + 40 +29.0 + 40 +30.0 + 40 +30.0 + 40 +30.0 + 40 +31.0 + 40 +31.0 + 40 +31.0 + 40 +32.0 + 40 +32.0 + 40 +32.0 + 40 +33.0 + 40 +33.0 + 40 +33.0 + 40 +34.0 + 40 +34.0 + 40 +34.0 + 40 +35.0 + 40 +35.0 + 40 +35.0 + 40 +36.0 + 40 +36.0 + 40 +36.0 + 40 +37.0 + 40 +37.0 + 40 +37.0 + 40 +38.0 + 40 +38.0 + 40 +38.0 + 40 +39.0 + 40 +39.0 + 40 +39.0 + 40 +40.0 + 40 +40.0 + 40 +40.0 + 40 +41.0 + 40 +41.0 + 40 +41.0 + 40 +42.0 + 40 +42.0 + 40 +42.0 + 40 +43.0 + 40 +43.0 + 40 +43.0 + 40 +44.0 + 40 +44.0 + 40 +44.0 + 40 +45.0 + 40 +45.0 + 40 +45.0 + 40 +46.0 + 40 +46.0 + 40 +46.0 + 40 +47.0 + 40 +47.0 + 40 +47.0 + 40 +48.0 + 40 +48.0 + 40 +48.0 + 40 +49.0 + 40 +49.0 + 40 +49.0 + 40 +50.0 + 40 +50.0 + 40 +50.0 + 40 +51.0 + 40 +51.0 + 40 +51.0 + 40 +52.0 + 40 +52.0 + 40 +52.0 + 40 +53.0 + 40 +53.0 + 40 +53.0 + 40 +54.0 + 40 +54.0 + 40 +54.0 + 40 +55.0 + 40 +55.0 + 40 +55.0 + 40 +56.0 + 40 +56.0 + 40 +56.0 + 40 +57.0 + 40 +57.0 + 40 +57.0 + 40 +58.0 + 40 +58.0 + 40 +58.0 + 40 +59.0 + 40 +59.0 + 40 +59.0 + 40 +60.0 + 40 +60.0 + 40 +60.0 + 40 +61.0 + 40 +61.0 + 40 +61.0 + 40 +62.0 + 40 +62.0 + 40 +62.0 + 40 +63.0 + 40 +63.0 + 40 +63.0 + 40 +64.0 + 40 +64.0 + 40 +64.0 + 40 +65.0 + 40 +65.0 + 40 +65.0 + 40 +66.0 + 40 +66.0 + 40 +66.0 + 40 +67.0 + 40 +67.0 + 40 +67.0 + 40 +68.0 + 40 +68.0 + 40 +68.0 + 40 +69.0 + 40 +69.0 + 40 +69.0 + 40 +70.0 + 40 +70.0 + 40 +70.0 + 40 +71.0 + 40 +71.0 + 40 +71.0 + 40 +72.0 + 40 +72.0 + 40 +72.0 + 40 +73.0 + 40 +73.0 + 40 +73.0 + 40 +74.0 + 40 +74.0 + 40 +74.0 + 40 +75.0 + 40 +75.0 + 40 +75.0 + 40 +76.0 + 40 +76.0 + 40 +76.0 + 40 +77.0 + 40 +77.0 + 40 +77.0 + 40 +78.0 + 40 +78.0 + 40 +78.0 + 40 +79.0 + 40 +79.0 + 40 +79.0 + 40 +80.0 + 40 +80.0 + 40 +80.0 + 40 +81.0 + 40 +81.0 + 40 +81.0 + 40 +82.0 + 40 +82.0 + 40 +82.0 + 40 +83.0 + 40 +83.0 + 40 +83.0 + 40 +84.0 + 40 +84.0 + 40 +84.0 + 40 +85.0 + 40 +85.0 + 40 +85.0 + 40 +86.0 + 40 +86.0 + 40 +86.0 + 40 +87.0 + 40 +87.0 + 40 +87.0 + 40 +88.0 + 40 +88.0 + 40 +88.0 + 40 +89.0 + 40 +89.0 + 40 +89.0 + 40 +90.0 + 40 +90.0 + 40 +90.0 + 40 +91.0 + 40 +91.0 + 40 +91.0 + 40 +92.0 + 40 +92.0 + 40 +92.0 + 40 +93.0 + 40 +93.0 + 40 +93.0 + 40 +93.0 + 10 +20.96821509328973 + 20 +-27.88604482453022 + 10 +20.96821509328973 + 20 +-27.86088514265015 + 10 +20.96821509328973 + 20 +-27.835726557626 + 10 +20.96821509328973 + 20 +-27.80697247878361 + 10 +20.96821509328973 + 20 +-27.75305981491712 + 10 +20.96821509328973 + 20 +-27.69914715105066 + 10 +20.96821509328973 + 20 +-27.64523448718412 + 10 +20.96821509328973 + 20 +-27.53381476248887 + 10 +20.96821509328973 + 20 +-27.41880173768721 + 10 +20.96821509328973 + 20 +-27.29659882210505 + 10 +20.96821509328973 + 20 +-27.05219518465259 + 10 +20.96462069632748 + 20 +-26.7898195623887 + 10 +20.96102629936513 + 20 +-26.50947414902528 + 10 +20.95743190240288 + 20 +-25.9487822254425 + 10 +20.95024420533425 + 20 +-25.31620675003766 + 10 +20.94664980837196 + 20 +-24.62612531066002 + 10 +20.94664980837196 + 20 +-24.56861824983124 + 10 +20.94664980837196 + 20 +-24.51111118900245 + 10 +20.94664980837196 + 20 +-24.45360522502961 + 10 +20.94664980837196 + 20 +-24.45360522502961 + 10 +20.27094485998752 + 20 +-24.84896476005062 + 10 +20.27094485998752 + 20 +-24.84896476005062 + 10 +20.27094485998752 + 20 +-24.84896476005062 + 10 +20.30329223893624 + 20 +-27.8249444635951 + 10 +20.30329223893624 + 20 +-27.8249444635951 + 10 +20.30329223893624 + 20 +-27.91120450641029 + 10 +20.23500308407647 + 20 +-27.98308805823232 + 10 +20.14874304126129 + 20 +-27.98308805823232 + 10 +20.14874304126129 + 20 +-27.98308805823232 + 10 +20.14874304126129 + 20 +-27.98308805823232 + 10 +20.14874304126129 + 20 +-27.98308805823232 + 10 +20.14874304126129 + 20 +-27.98308805823232 + 10 +20.14514864429899 + 20 +-27.98308805823232 + 10 +20.14514864429899 + 20 +-27.98308805823232 + 10 +20.02294682557279 + 20 +-27.98308805823232 + 10 +19.89715060988429 + 20 +-27.98308805823232 + 10 +19.77494879115807 + 20 +-27.98308805823232 + 10 +19.77494879115807 + 20 +-27.98308805823232 + 10 +19.40474893801711 + 20 +-27.97949366127002 + 10 +19.40474893801711 + 20 +-27.97949366127002 + 10 +19.40474893801711 + 20 +-27.97949366127002 + 10 +18.66075373791702 + 20 +-27.97230486734545 + 10 +18.66075373791702 + 20 +-27.97230486734545 + 10 +18.66075373791702 + 20 +-27.97230486734545 + 10 +14.20397352495284 + 20 +-27.92198660044121 + 10 +14.20397352495284 + 20 +-27.92198660044121 + 10 +14.15006086108636 + 20 +-27.92198660044121 + 10 +14.10693029125082 + 20 +-27.87885603060562 + 10 +14.10693029125082 + 20 +-27.82494336673913 + 10 +14.10693029125082 + 20 +-27.82494336673913 + 10 +14.10693029125082 + 20 +-27.82494336673913 + 10 +14.10693029125082 + 20 +-27.82494336673913 + 10 +14.09974149732622 + 20 +-27.34332378890291 + 10 +14.09255380025754 + 20 +-26.85810981410441 + 10 +14.0889594032953 + 20 +-26.37649023626818 + 10 +14.0889594032953 + 20 +-26.37649023626818 + 10 +14.07817730926435 + 20 +-24.9280371057972 + 10 +14.07817730926435 + 20 +-24.9280371057972 + 10 +14.07817730926435 + 20 +-24.9280371057972 + 10 +14.07458291230205 + 20 +-24.20560719061485 + 10 +14.07458291230205 + 20 +-24.20560719061485 + 10 +14.07458291230205 + 20 +-24.20560719061485 + 10 +14.07458291230205 + 20 +-23.48317727543256 + 10 +14.07458291230205 + 20 +-23.48317727543256 + 10 +14.07458291230205 + 20 +-23.48317727543256 + 10 +14.07098851533978 + 20 +-22.0347241449616 + 10 +14.07098851533978 + 20 +-22.0347241449616 + 10 +14.07098851533978 + 20 +-21.95924619617729 + 10 +14.13208997313083 + 20 +-21.89814583524215 + 10 +14.20756682505917 + 20 +-21.8945514382799 + 10 +14.20756682505917 + 20 +-21.8945514382799 + 10 +14.20756682505917 + 20 +-21.8945514382799 + 10 +14.20756682505917 + 20 +-21.8945514382799 + 10 +14.20756682505917 + 20 +-21.8945514382799 + 10 +14.95156202515929 + 20 +-21.8945514382799 + 10 +14.95156202515929 + 20 +-21.8945514382799 + 10 +14.95156202515929 + 20 +-21.8945514382799 + 10 +15.69555722525944 + 20 +-21.89814583524215 + 10 +15.69555722525944 + 20 +-21.89814583524215 + 10 +15.69555722525944 + 20 +-21.89814583524215 + 10 +17.17995213164141 + 20 +-21.90174023220445 + 10 +17.17995213164141 + 20 +-21.90174023220445 + 10 +17.17995213164141 + 20 +-21.90174023220445 + 10 +18.66434703802335 + 20 +-21.91611672319767 + 10 +18.66434703802335 + 20 +-21.91611672319767 + 10 +18.66434703802335 + 20 +-21.91611672319767 + 10 +19.40834223812345 + 20 +-21.9268988172286 + 10 +19.40834223812345 + 20 +-21.9268988172286 + 10 +19.65634027253817 + 20 +-21.9304932141909 + 10 +19.9043383069529 + 20 +-21.93408761115314 + 10 +20.15233743822359 + 20 +-21.93768091125952 + 10 +20.15233743822359 + 20 +-21.93768091125952 + 10 +20.15233743822359 + 20 +-21.93768091125952 + 10 +20.15233743822359 + 20 +-21.93768091125952 + 10 +20.2062501020901 + 20 +-21.93768091125952 + 10 +20.24938067192566 + 20 +-21.98081148109509 + 10 +20.24938067192566 + 20 +-22.0347241449616 + 10 +20.24938067192566 + 20 +-22.0347241449616 + 10 +20.26375716291886 + 20 +-23.45801869040843 + 10 +20.26375716291886 + 20 +-23.45801869040843 + 10 +20.26375716291886 + 20 +-23.45801869040843 + 10 +20.94665090522787 + 20 +-23.05906475842514 + 10 +20.94665090522787 + 20 +-23.05906475842514 + 10 +20.94665090522787 + 20 +-22.84341410295913 + 10 +20.94665090522787 + 20 +-22.62057465356856 + 10 +20.94305650826562 + 20 +-22.3977363010339 + 10 +20.94305650826562 + 20 +-22.29709976722549 + 10 +20.94305650826562 + 20 +-22.20005653352342 + 10 +20.94305650826562 + 20 +-22.09941999971498 + 10 +20.94305650826562 + 20 +-22.04910173281077 + 10 +20.94305650826562 + 20 +-21.99878346590652 + 10 +20.94305650826562 + 20 +-21.94846410214637 + 10 +20.94305650826562 + 20 +-21.9233044202663 + 10 +20.93946211130333 + 20 +-21.90533353231078 + 10 +20.93946211130333 + 20 +-21.88376934424898 + 10 +20.93946211130333 + 20 +-21.86220405933118 + 10 +20.93227331737876 + 20 +-21.84063877441336 + 10 +20.93227331737876 + 20 +-21.82266788645789 + 10 +20.90352033539231 + 20 +-21.6573354978961 + 10 +20.81007149865253 + 20 +-21.49919190325886 + 10 +20.66989879197081 + 20 +-21.39496097248815 + 10 +20.60160963711112 + 20 +-21.34104830862167 + 10 +20.52253729136449 + 20 +-21.30151213574835 + 10 +20.43987164551158 + 20 +-21.2763535507242 + 10 +20.41830636059379 + 20 +-21.27275915376195 + 10 +20.39674107567602 + 20 +-21.26557090826532 + 10 +20.37877018772049 + 20 +-21.26197705973103 + 10 +20.3572049028027 + 20 +-21.25838266276873 + 10 +20.34282841180953 + 20 +-21.25478881423444 + 10 +20.31766872992941 + 20 +-21.25119441727214 + 10 +20.31766872992941 + 20 +-21.25119441727214 + 10 +20.23859638418281 + 20 +-21.24760002030985 + 10 +20.23859638418281 + 20 +-21.24760002030985 + 10 +20.23859638418281 + 20 +-21.24760002030985 + 10 +20.16671283236085 + 20 +-21.24760002030985 + 10 +20.16671283236085 + 20 +-21.24760002030985 + 10 +19.95465657385714 + 20 +-21.24760002030985 + 10 +19.7426003153534 + 20 +-21.24400562334755 + 10 +19.52694965988742 + 20 +-21.24400562334755 + 10 +18.66434703802335 + 20 +-21.24041122638525 + 10 +17.77299143417288 + 20 +-21.22603473539205 + 10 +16.8672582424733 + 20 +-21.21884648989544 + 10 +15.96152505077372 + 20 +-21.20806384743653 + 10 +15.0414164649368 + 20 +-21.20446999890224 + 10 +14.12130678224399 + 20 +-21.20446999890224 + 10 +13.91643822080889 + 20 +-21.20446999890224 + 10 +13.7187584532984 + 20 +-21.29432443867972 + 10 +13.58218014357898 + 20 +-21.44528033624829 + 10 +13.51389098871924 + 20 +-21.5207582850326 + 10 +13.45997832485275 + 20 +-21.60701832784783 + 10 +13.42763094590403 + 20 +-21.70406156154993 + 10 +13.40966005794851 + 20 +-21.75078543149185 + 10 +13.39887796391763 + 20 +-21.80110479525198 + 10 +13.39528356695534 + 20 +-21.84782866519393 + 10 +13.39528356695534 + 20 +-21.8550174591185 + 10 +13.39168916999304 + 20 +-21.85861075922483 + 10 +13.39168916999304 + 20 +-21.86579955314942 + 10 +13.39168916999304 + 20 +-21.86579955314942 + 10 +13.39168916999304 + 20 +-21.88736483806719 + 10 +13.39168916999304 + 20 +-21.88736483806719 + 10 +13.39168916999304 + 20 +-21.88736483806719 + 10 +13.38809477303074 + 20 +-21.9412775019337 + 10 +13.38809477303074 + 20 +-21.9412775019337 + 10 +13.38809477303074 + 20 +-21.944871898896 + 10 +13.38809477303074 + 20 +-21.944871898896 + 10 +13.38809477303074 + 20 +-21.944871898896 + 10 +13.38809477303074 + 20 +-21.944871898896 + 10 +13.38809477303074 + 20 +-21.944871898896 + 10 +13.38809477303074 + 20 +-21.944871898896 + 10 +13.38809477303074 + 20 +-21.944871898896 + 10 +13.38809477303074 + 20 +-21.9664371838138 + 10 +13.38809477303074 + 20 +-21.9664371838138 + 10 +13.38809477303074 + 20 +-21.9664371838138 + 10 +13.38809477303074 + 20 +-22.00956775364938 + 10 +13.38809477303074 + 20 +-22.00956775364938 + 10 +13.38809477303074 + 20 +-22.46962204656779 + 10 +13.39168916999304 + 20 +-22.92608303937989 + 10 +13.39887686706167 + 20 +-23.37894963522972 + 10 +13.40965896109259 + 20 +-24.28468282692933 + 10 +13.41684775501719 + 20 +-25.17603843077978 + 10 +13.42762984904812 + 20 +-26.0386410526438 + 10 +13.43122424601036 + 20 +-26.46994236357583 + 10 +13.43481864297266 + 20 +-26.89405488058325 + 10 +13.43841194307902 + 20 +-27.31097970052206 + 10 +13.44200634004132 + 20 +-27.51944156206349 + 10 +13.44200634004132 + 20 +-27.72431012349858 + 10 +13.44560073700356 + 20 +-27.92558428797138 + 10 +13.44560073700356 + 20 +-28.09091667653317 + 10 +13.51029549490103 + 20 +-28.24906027117039 + 10 +13.61812082263403 + 20 +-28.37126208989659 + 10 +13.67203348650052 + 20 +-28.43236354768768 + 10 +13.73313494429158 + 20 +-28.48268181459192 + 10 +13.80501849611359 + 20 +-28.52221798746521 + 10 +13.80501849611359 + 20 +-28.52221798746521 + 10 +13.85893115998008 + 20 +-28.54737766934527 + 10 +13.85893115998008 + 20 +-28.54737766934527 + 10 +13.85893115998008 + 20 +-28.54737766934527 + 10 +13.91284382384659 + 20 +-28.56894295426306 + 10 +13.91284382384659 + 20 +-28.56894295426306 + 10 +13.94878559975761 + 20 +-28.57972504829399 + 10 +13.9883217726309 + 20 +-28.59050823918088 + 10 +14.02426354854192 + 20 +-28.59410263614313 + 10 +14.03145234246646 + 20 +-28.59410263614313 + 10 +14.04223443649739 + 20 +-28.59769703310543 + 10 +14.04942323042198 + 20 +-28.59769703310543 + 10 +14.04942323042198 + 20 +-28.59769703310543 + 10 +14.085365006333 + 20 +-28.60129143006773 + 10 +14.085365006333 + 20 +-28.60129143006773 + 10 +14.085365006333 + 20 +-28.60129143006773 + 10 +14.12130678224399 + 20 +-28.60488582703002 + 10 +14.12130678224399 + 20 +-28.60488582703002 + 10 +14.12130678224399 + 20 +-28.60488582703002 + 10 +14.12849557616859 + 20 +-28.60488582703002 + 10 +14.12849557616859 + 20 +-28.60488582703002 + 10 +14.12849557616859 + 20 +-28.60488582703002 + 10 +14.13208997313083 + 20 +-28.60488582703002 + 10 +14.13208997313083 + 20 +-28.60488582703002 + 10 +14.13208997313083 + 20 +-28.60488582703002 + 10 +14.15006086108636 + 20 +-28.60488582703002 + 10 +14.15006086108636 + 20 +-28.60488582703002 + 10 +14.19678473102825 + 20 +-28.60488582703002 + 10 +14.24350969782616 + 20 +-28.60488582703002 + 10 +14.29382796473038 + 20 +-28.60488582703002 + 10 +14.38727680147015 + 20 +-28.60488582703002 + 10 +14.48072563820996 + 20 +-28.60848022399232 + 10 +14.57417337809382 + 20 +-28.60848022399232 + 10 +14.94796762819702 + 20 +-28.61207462095462 + 10 +15.30738538730707 + 20 +-28.61566901791689 + 10 +15.65242665542382 + 20 +-28.61926231802325 + 10 +16.34250919165745 + 20 +-28.62645111194782 + 10 +16.9750835702063 + 20 +-28.63363880901645 + 10 +17.53577549378911 + 20 +-28.64082760294104 + 10 +18.09646741737192 + 20 +-28.64801639686564 + 10 +18.58527578913274 + 20 +-28.65160969697197 + 10 +18.98782302122239 + 20 +-28.65160969697197 + 10 +19.7929185822576 + 20 +-28.65520409393422 + 10 +20.252973972032 + 20 +-28.65520409393422 + 10 +20.252973972032 + 20 +-28.65520409393422 + 10 +20.252973972032 + 20 +-28.65520409393422 + 10 +20.27813365391211 + 20 +-28.65520409393422 + 10 +20.3284519208163 + 20 +-28.65160969697197 + 10 +20.37517579075822 + 20 +-28.64442090304738 + 10 +20.45065373954251 + 20 +-28.63723320597875 + 10 +20.53691378235774 + 20 +-28.59769703310543 + 10 +20.62317382517297 + 20 +-28.56175525719444 + 10 +20.72740475594368 + 20 +-28.49706049929702 + 10 +20.81725919572116 + 20 +-28.39282847167033 + 10 +20.86038976555675 + 20 +-28.33891580780386 + 10 +20.89992484157404 + 20 +-28.27781435001278 + 10 +20.93227331737876 + 20 +-28.20233749808444 + 10 +20.93946211130333 + 20 +-28.18436661012892 + 10 +20.94664980837196 + 20 +-28.16639572217344 + 10 +20.95383860229655 + 20 +-28.14483043725565 + 10 +20.96102739622114 + 20 +-28.12685954930013 + 10 +20.96462069632748 + 20 +-28.10529426438236 + 10 +20.97180949025202 + 20 +-28.08372897946454 + 10 +20.97899828417662 + 20 +-28.06216369454675 + 10 +20.97899828417662 + 20 +-28.04419280659128 + 10 +20.98259158428295 + 20 +-28.02262752167346 + 10 +20.98259158428295 + 20 +-28.01184542764253 + 10 +20.98618598124525 + 20 +-28.00465663371799 + 10 +20.98618598124525 + 20 +-27.99387453968706 + 10 +20.98618598124525 + 20 +-27.98309244565613 + 10 +20.98618598124525 + 20 +-27.96871485780697 + 10 +20.98618598124525 + 20 +-27.95793276377604 + 10 +20.98618598124525 + 20 +-27.94715066974512 + 10 +20.98618598124525 + 20 +-27.93277308189598 + 10 +20.98618598124525 + 20 +-27.91839659090275 + 10 +20.98618598124525 + 20 +-27.91839659090275 + 10 +20.98618598124525 + 20 +-27.91480219394046 + 10 +20.98618598124525 + 20 +-27.91480219394046 + 10 +20.98618598124525 + 20 +-27.91480219394046 + 10 +20.98618598124525 + 20 +-27.91480219394046 + 10 +20.98618598124525 + 20 +-27.91480219394046 + 10 +20.98618598124525 + 20 +-27.91480219394046 + 10 +20.98618598124525 + 20 +-27.90402009990956 + 10 +20.98618598124525 + 20 +-27.90402009990956 + 10 +20.98618598124525 + 20 +-27.90402009990956 + 10 +20.96821509328973 + 20 +-27.88604482453022 + 10 +20.96821509328973 + 20 +-27.88604482453022 + 97 + 0 + 97 + 0 + 75 + 2 + 76 + 1 + 98 + 0 + 0 +SPLINE + 5 +4A +330 +41 +100 +AcDbEntity + 8 +Layer 1 +370 + 0 +100 +AcDbSpline +210 +0.0 +220 +0.0 +230 +1.0 + 70 + 11 + 71 + 3 + 72 + 218 + 73 + 214 + 74 + 0 + 42 +0.0000000001 + 43 +0.0000000001 + 12 +1.0 + 22 +0.0 + 32 +0.0 + 13 +1.0 + 23 +0.0 + 33 +0.0 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +1.0 + 40 +1.0 + 40 +1.0 + 40 +2.0 + 40 +2.0 + 40 +2.0 + 40 +3.0 + 40 +3.0 + 40 +3.0 + 40 +4.0 + 40 +4.0 + 40 +4.0 + 40 +5.0 + 40 +5.0 + 40 +5.0 + 40 +6.0 + 40 +6.0 + 40 +6.0 + 40 +7.0 + 40 +7.0 + 40 +7.0 + 40 +8.0 + 40 +8.0 + 40 +8.0 + 40 +9.0 + 40 +9.0 + 40 +9.0 + 40 +10.0 + 40 +10.0 + 40 +10.0 + 40 +11.0 + 40 +11.0 + 40 +11.0 + 40 +12.0 + 40 +12.0 + 40 +12.0 + 40 +13.0 + 40 +13.0 + 40 +13.0 + 40 +14.0 + 40 +14.0 + 40 +14.0 + 40 +15.0 + 40 +15.0 + 40 +15.0 + 40 +16.0 + 40 +16.0 + 40 +16.0 + 40 +17.0 + 40 +17.0 + 40 +17.0 + 40 +18.0 + 40 +18.0 + 40 +18.0 + 40 +19.0 + 40 +19.0 + 40 +19.0 + 40 +20.0 + 40 +20.0 + 40 +20.0 + 40 +21.0 + 40 +21.0 + 40 +21.0 + 40 +22.0 + 40 +22.0 + 40 +22.0 + 40 +23.0 + 40 +23.0 + 40 +23.0 + 40 +24.0 + 40 +24.0 + 40 +24.0 + 40 +25.0 + 40 +25.0 + 40 +25.0 + 40 +26.0 + 40 +26.0 + 40 +26.0 + 40 +27.0 + 40 +27.0 + 40 +27.0 + 40 +28.0 + 40 +28.0 + 40 +28.0 + 40 +29.0 + 40 +29.0 + 40 +29.0 + 40 +30.0 + 40 +30.0 + 40 +30.0 + 40 +31.0 + 40 +31.0 + 40 +31.0 + 40 +32.0 + 40 +32.0 + 40 +32.0 + 40 +33.0 + 40 +33.0 + 40 +33.0 + 40 +34.0 + 40 +34.0 + 40 +34.0 + 40 +35.0 + 40 +35.0 + 40 +35.0 + 40 +36.0 + 40 +36.0 + 40 +36.0 + 40 +37.0 + 40 +37.0 + 40 +37.0 + 40 +38.0 + 40 +38.0 + 40 +38.0 + 40 +39.0 + 40 +39.0 + 40 +39.0 + 40 +40.0 + 40 +40.0 + 40 +40.0 + 40 +41.0 + 40 +41.0 + 40 +41.0 + 40 +42.0 + 40 +42.0 + 40 +42.0 + 40 +43.0 + 40 +43.0 + 40 +43.0 + 40 +44.0 + 40 +44.0 + 40 +44.0 + 40 +45.0 + 40 +45.0 + 40 +45.0 + 40 +46.0 + 40 +46.0 + 40 +46.0 + 40 +47.0 + 40 +47.0 + 40 +47.0 + 40 +48.0 + 40 +48.0 + 40 +48.0 + 40 +49.0 + 40 +49.0 + 40 +49.0 + 40 +50.0 + 40 +50.0 + 40 +50.0 + 40 +51.0 + 40 +51.0 + 40 +51.0 + 40 +52.0 + 40 +52.0 + 40 +52.0 + 40 +53.0 + 40 +53.0 + 40 +53.0 + 40 +54.0 + 40 +54.0 + 40 +54.0 + 40 +55.0 + 40 +55.0 + 40 +55.0 + 40 +56.0 + 40 +56.0 + 40 +56.0 + 40 +57.0 + 40 +57.0 + 40 +57.0 + 40 +58.0 + 40 +58.0 + 40 +58.0 + 40 +59.0 + 40 +59.0 + 40 +59.0 + 40 +60.0 + 40 +60.0 + 40 +60.0 + 40 +61.0 + 40 +61.0 + 40 +61.0 + 40 +62.0 + 40 +62.0 + 40 +62.0 + 40 +63.0 + 40 +63.0 + 40 +63.0 + 40 +64.0 + 40 +64.0 + 40 +64.0 + 40 +65.0 + 40 +65.0 + 40 +65.0 + 40 +66.0 + 40 +66.0 + 40 +66.0 + 40 +67.0 + 40 +67.0 + 40 +67.0 + 40 +68.0 + 40 +68.0 + 40 +68.0 + 40 +69.0 + 40 +69.0 + 40 +69.0 + 40 +70.0 + 40 +70.0 + 40 +70.0 + 40 +71.0 + 40 +71.0 + 40 +71.0 + 40 +71.0 + 10 +22.71858001889789 + 20 +-22.59541497168842 + 30 +0.0 + 10 +22.6395076731513 + 20 +-22.52353141986643 + 30 +0.0 + 10 +22.51730695128104 + 20 +-22.49477843788001 + 30 +0.0 + 10 +22.41307492365437 + 20 +-22.52712581682873 + 30 +0.0 + 10 +22.40588612972978 + 20 +-22.52712581682873 + 30 +0.0 + 10 +22.40229282962344 + 20 +-22.53072021379103 + 30 +0.0 + 10 +22.39510403569885 + 20 +-22.53431461075332 + 30 +0.0 + 10 +22.39151073559252 + 20 +-22.53790900771562 + 30 +0.0 + 10 +22.38432194166797 + 20 +-22.53790900771562 + 30 +0.0 + 10 +22.38072644784971 + 20 +-22.53790900771562 + 30 +0.0 + 10 +22.38072644784971 + 20 +-22.53790900771562 + 30 +0.0 + 10 +22.35556676596962 + 20 +-22.55228549870879 + 30 +0.0 + 10 +22.35556676596962 + 20 +-22.55228549870879 + 30 +0.0 + 10 +22.35556676596962 + 20 +-22.55228549870879 + 30 +0.0 + 10 +22.34118917812043 + 20 +-22.55947429263339 + 30 +0.0 + 10 +22.34118917812043 + 20 +-22.55947429263339 + 30 +0.0 + 10 +22.34118917812043 + 20 +-22.55947429263339 + 30 +0.0 + 10 +22.34118917812043 + 20 +-22.55947429263339 + 30 +0.0 + 10 +22.34118917812043 + 20 +-22.55947429263339 + 30 +0.0 + 10 +22.34118917812043 + 20 +-22.55947429263339 + 30 +0.0 + 10 +22.34118917812043 + 20 +-22.55947429263339 + 30 +0.0 + 10 +22.34118917812043 + 20 +-22.55947429263339 + 30 +0.0 + 10 +22.34118917812043 + 20 +-22.55947429263339 + 30 +0.0 + 10 +22.33400038419589 + 20 +-22.56306868959569 + 30 +0.0 + 10 +22.33400038419589 + 20 +-22.56306868959569 + 30 +0.0 + 10 +22.33400038419589 + 20 +-22.56306868959569 + 30 +0.0 + 10 +22.23336385038748 + 20 +-22.62057575042448 + 30 +0.0 + 10 +22.23336385038748 + 20 +-22.62057575042448 + 30 +0.0 + 10 +22.23336385038748 + 20 +-22.62057575042448 + 30 +0.0 + 10 +22.02849528895237 + 20 +-22.7391831721884 + 30 +0.0 + 10 +22.02849528895237 + 20 +-22.7391831721884 + 30 +0.0 + 10 +22.02849528895237 + 20 +-22.7391831721884 + 30 +0.0 + 10 +21.61875816608219 + 20 +-22.9728047156099 + 30 +0.0 + 10 +21.61875816608219 + 20 +-22.9728047156099 + 30 +0.0 + 10 +21.61875816608219 + 20 +-22.9728047156099 + 30 +0.0 + 10 +19.98340736213165 + 20 +-23.91088528625826 + 30 +0.0 + 10 +19.98340736213165 + 20 +-23.91088528625826 + 30 +0.0 + 10 +19.98340736213165 + 20 +-23.91088528625826 + 30 +0.0 + 10 +18.34446216121877 + 20 +-24.84537145994424 + 30 +0.0 + 10 +18.34446216121877 + 20 +-24.84537145994424 + 30 +0.0 + 10 +18.34446216121877 + 20 +-24.84537145994424 + 30 +0.0 + 10 +16.70551696030596 + 20 +-25.7798576336303 + 30 +0.0 + 10 +16.70551696030596 + 20 +-25.7798576336303 + 30 +0.0 + 10 +16.66238639047032 + 20 +-25.80501731551036 + 30 +0.0 + 10 +16.60847372660383 + 20 +-25.79423412462352 + 30 +0.0 + 10 +16.57972074461744 + 20 +-25.75829234871253 + 30 +0.0 + 10 +16.57972074461744 + 20 +-25.75829234871253 + 30 +0.0 + 10 +16.57972074461744 + 20 +-25.75829234871253 + 30 +0.0 + 10 +16.57972074461744 + 20 +-25.75829234871253 + 30 +0.0 + 10 +16.57972074461744 + 20 +-25.75829234871253 + 30 +0.0 + 10 +16.57972074461744 + 20 +-25.75829234871253 + 30 +0.0 + 10 +16.57972074461744 + 20 +-25.75829234871253 + 30 +0.0 + 10 +16.43595364097342 + 20 +-25.58936556318844 + 30 +0.0 + 10 +16.29218653732945 + 20 +-25.42043987452032 + 30 +0.0 + 10 +16.14841943368543 + 20 +-25.24791869203391 + 30 +0.0 + 10 +16.14841943368543 + 20 +-25.24791869203391 + 30 +0.0 + 10 +15.71711812275339 + 20 +-24.73754503535532 + 30 +0.0 + 10 +15.71711812275339 + 20 +-24.73754503535532 + 30 +0.0 + 10 +15.71711812275339 + 20 +-24.73754503535532 + 30 +0.0 + 10 +15.60929279502041 + 20 +-24.60815442270453 + 30 +0.0 + 10 +15.60929279502041 + 20 +-24.60815442270453 + 30 +0.0 + 10 +15.60210400109582 + 20 +-24.5973723286736 + 30 +0.0 + 10 +15.59132190706489 + 20 +-24.58658913778671 + 30 +0.0 + 10 +15.5841331131403 + 20 +-24.57940144071808 + 30 +0.0 + 10 +15.5841331131403 + 20 +-24.57940144071808 + 30 +0.0 + 10 +15.56256782822253 + 20 +-24.55783615580031 + 30 +0.0 + 10 +15.56256782822253 + 20 +-24.55783615580031 + 30 +0.0 + 10 +15.56256782822253 + 20 +-24.55783615580031 + 30 +0.0 + 10 +15.54100254330473 + 20 +-24.53986526784481 + 30 +0.0 + 10 +15.54100254330473 + 20 +-24.53986526784481 + 30 +0.0 + 10 +15.53381374938014 + 20 +-24.53267647392022 + 30 +0.0 + 10 +15.52303165534921 + 20 +-24.52908317381389 + 30 +0.0 + 10 +15.51584286142462 + 20 +-24.52189437988929 + 30 +0.0 + 10 +15.45114810352723 + 20 +-24.47876381005375 + 30 +0.0 + 10 +15.36848136081835 + 20 +-24.46079292209823 + 30 +0.0 + 10 +15.28941011192771 + 20 +-24.46798171602283 + 30 +0.0 + 10 +15.24987393905442 + 20 +-24.47157611298513 + 30 +0.0 + 10 +15.21393216314343 + 20 +-24.4859526039783 + 30 +0.0 + 10 +15.17799038723241 + 20 +-24.50392349193382 + 30 +0.0 + 10 +15.17080159330787 + 20 +-24.50751788889612 + 30 +0.0 + 10 +15.16361389623919 + 20 +-24.51111228585836 + 30 +0.0 + 10 +15.15642510231464 + 20 +-24.51470558596475 + 30 +0.0 + 10 +15.15283070535234 + 20 +-24.51470558596475 + 30 +0.0 + 10 +15.14564300828371 + 20 +-24.52189437988929 + 30 +0.0 + 10 +15.14204861132142 + 20 +-24.52548767999562 + 30 +0.0 + 10 +15.14204861132142 + 20 +-24.52548767999562 + 30 +0.0 + 10 +15.1240777233659 + 20 +-24.53626977402655 + 30 +0.0 + 10 +15.1240777233659 + 20 +-24.53626977402655 + 30 +0.0 + 10 +15.1240777233659 + 20 +-24.53626977402655 + 30 +0.0 + 10 +15.12048332640362 + 20 +-24.53986417098885 + 30 +0.0 + 10 +15.12048332640362 + 20 +-24.53986417098885 + 30 +0.0 + 10 +15.12048332640362 + 20 +-24.53986417098885 + 30 +0.0 + 10 +15.12048332640362 + 20 +-24.53986417098885 + 30 +0.0 + 10 +15.12048332640362 + 20 +-24.53986417098885 + 30 +0.0 + 10 +15.12048332640362 + 20 +-24.53986417098885 + 30 +0.0 + 10 +15.12048332640362 + 20 +-24.53986417098885 + 30 +0.0 + 10 +15.12048332640362 + 20 +-24.53986417098885 + 30 +0.0 + 10 +15.12048332640362 + 20 +-24.53986417098885 + 30 +0.0 + 10 +15.11688892944133 + 20 +-24.54345856795114 + 30 +0.0 + 10 +15.11688892944133 + 20 +-24.54345856795114 + 30 +0.0 + 10 +15.11688892944133 + 20 +-24.54345856795114 + 30 +0.0 + 10 +15.1061068354104 + 20 +-24.55424066198207 + 30 +0.0 + 10 +15.1061068354104 + 20 +-24.55424066198207 + 30 +0.0 + 10 +15.09891804148586 + 20 +-24.56142945590664 + 30 +0.0 + 10 +15.09173034441718 + 20 +-24.56861715297527 + 30 +0.0 + 10 +15.08454155049263 + 20 +-24.57580594689987 + 30 +0.0 + 10 +15.03422328358844 + 20 +-24.63331300772865 + 30 +0.0 + 10 +15.00546920474608 + 20 +-24.70879095651296 + 30 +0.0 + 10 +15.00546920474608 + 20 +-24.7878622054036 + 30 +0.0 + 10 +15.00546920474608 + 20 +-24.82739837827689 + 30 +0.0 + 10 +15.01265799867062 + 20 +-24.86334015418786 + 30 +0.0 + 10 +15.02703448966385 + 20 +-24.89928193009885 + 30 +0.0 + 10 +15.03062888662609 + 20 +-24.91006402412977 + 30 +0.0 + 10 +15.03422328358844 + 20 +-24.91725281805437 + 30 +0.0 + 10 +15.03781658369477 + 20 +-24.92444161197896 + 30 +0.0 + 10 +15.03781658369477 + 20 +-24.92444161197896 + 30 +0.0 + 10 +15.04500537761932 + 20 +-24.93881810297216 + 30 +0.0 + 10 +15.04500537761932 + 20 +-24.93881810297216 + 30 +0.0 + 10 +15.04500537761932 + 20 +-24.93881810297216 + 30 +0.0 + 10 +15.05219417154391 + 20 +-24.94960019700309 + 30 +0.0 + 10 +15.05219417154391 + 20 +-24.94960019700309 + 30 +0.0 + 10 +15.05219417154391 + 20 +-24.94960019700309 + 30 +0.0 + 10 +15.05578856850621 + 20 +-24.96038229103402 + 30 +0.0 + 10 +15.05578856850621 + 20 +-24.96038229103402 + 30 +0.0 + 10 +15.05578856850621 + 20 +-24.96397668799631 + 30 +0.0 + 10 +15.0629773624308 + 20 +-24.97116438506494 + 30 +0.0 + 10 +15.06657066253713 + 20 +-24.97475878202719 + 30 +0.0 + 10 +15.06657066253713 + 20 +-24.97475878202719 + 30 +0.0 + 10 +15.07735275656804 + 20 +-24.99272966998271 + 30 +0.0 + 10 +15.07735275656804 + 20 +-24.99272966998271 + 30 +0.0 + 10 +15.07735275656804 + 20 +-24.99272966998271 + 30 +0.0 + 10 +15.08094715353033 + 20 +-24.99632406694501 + 30 +0.0 + 10 +15.08094715353033 + 20 +-24.99632406694501 + 30 +0.0 + 10 +15.08094715353033 + 20 +-24.99632406694501 + 30 +0.0 + 10 +15.08813594745493 + 20 +-25.00351286086955 + 30 +0.0 + 10 +15.08813594745493 + 20 +-25.00351286086955 + 30 +0.0 + 10 +15.08813594745493 + 20 +-25.00351286086955 + 30 +0.0 + 10 +15.30019220595861 + 20 +-25.2622940861712 + 30 +0.0 + 10 +15.30019220595861 + 20 +-25.2622940861712 + 30 +0.0 + 10 +15.30019220595861 + 20 +-25.2622940861712 + 30 +0.0 + 10 +15.72430472296605 + 20 +-25.77985543991841 + 30 +0.0 + 10 +15.72430472296605 + 20 +-25.77985543991841 + 30 +0.0 + 10 +15.72430472296605 + 20 +-25.77985543991841 + 30 +0.0 + 10 +16.14482393986716 + 20 +-26.29741679366563 + 30 +0.0 + 10 +16.14482393986716 + 20 +-26.29741679366563 + 30 +0.0 + 10 +16.14482393986716 + 20 +-26.29741679366563 + 30 +0.0 + 10 +16.24905487063787 + 20 +-26.42680740631643 + 30 +0.0 + 10 +16.24905487063787 + 20 +-26.42680740631643 + 30 +0.0 + 10 +16.24905487063787 + 20 +-26.42680740631643 + 30 +0.0 + 10 +16.30296753450435 + 20 +-26.49150216421384 + 30 +0.0 + 10 +16.30296753450435 + 20 +-26.49150216421384 + 30 +0.0 + 10 +16.31734402549758 + 20 +-26.50587865520707 + 30 +0.0 + 10 +16.32812721638447 + 20 +-26.52025514620024 + 30 +0.0 + 10 +16.34250370737767 + 20 +-26.53103833708714 + 30 +0.0 + 10 +16.37125668936412 + 20 +-26.55619801896723 + 30 +0.0 + 10 +16.40360516516876 + 20 +-26.57416890692275 + 30 +0.0 + 10 +16.43595254411745 + 20 +-26.58495100095362 + 30 +0.0 + 10 +16.45392343207297 + 20 +-26.59213979487822 + 30 +0.0 + 10 +16.47189432002849 + 20 +-26.59573309498455 + 30 +0.0 + 10 +16.48986520798396 + 20 +-26.59932749194685 + 30 +0.0 + 10 +16.49705400190853 + 20 +-26.59932749194685 + 30 +0.0 + 10 +16.50783609593946 + 20 +-26.60292188890915 + 30 +0.0 + 10 +16.51502488986405 + 20 +-26.60292188890915 + 30 +0.0 + 10 +16.51502488986405 + 20 +-26.60292188890915 + 30 +0.0 + 10 +16.51861928682635 + 20 +-26.60292188890915 + 30 +0.0 + 10 +16.51861928682635 + 20 +-26.60292188890915 + 30 +0.0 + 10 +16.51861928682635 + 20 +-26.60292188890915 + 30 +0.0 + 10 +16.52221368378865 + 20 +-26.60292188890915 + 30 +0.0 + 10 +16.5258080807509 + 20 +-26.60292188890915 + 30 +0.0 + 10 +16.5258080807509 + 20 +-26.60292188890915 + 30 +0.0 + 10 +16.54377896870642 + 20 +-26.60292188890915 + 30 +0.0 + 10 +16.54377896870642 + 20 +-26.60292188890915 + 30 +0.0 + 10 +16.59409723561066 + 20 +-26.60292188890915 + 30 +0.0 + 10 +16.64800989947712 + 20 +-26.58854539791592 + 30 +0.0 + 10 +16.69114046931269 + 20 +-26.56338571603586 + 30 +0.0 + 10 +16.69114046931269 + 20 +-26.56338571603586 + 30 +0.0 + 10 +16.89241463378552 + 20 +-26.44837159437823 + 30 +0.0 + 10 +16.89241463378552 + 20 +-26.44837159437823 + 30 +0.0 + 10 +16.89241463378552 + 20 +-26.44837159437823 + 30 +0.0 + 10 +17.30215065979974 + 20 +-26.21115565399446 + 30 +0.0 + 10 +17.30215065979974 + 20 +-26.21115565399446 + 30 +0.0 + 10 +17.30215065979974 + 20 +-26.21115565399446 + 30 +0.0 + 10 +18.11802941172186 + 20 +-25.74031817018916 + 30 +0.0 + 10 +18.11802941172186 + 20 +-25.74031817018916 + 30 +0.0 + 10 +18.11802941172186 + 20 +-25.74031817018916 + 30 +0.0 + 10 +19.7497858187101 + 20 +-24.79504990247223 + 30 +0.0 + 10 +19.7497858187101 + 20 +-24.79504990247223 + 30 +0.0 + 10 +19.7497858187101 + 20 +-24.79504990247223 + 30 +0.0 + 10 +21.38154332255433 + 20 +-23.84978163475524 + 30 +0.0 + 10 +21.38154332255433 + 20 +-23.84978163475524 + 30 +0.0 + 10 +21.38154332255433 + 20 +-23.84978163475524 + 30 +0.0 + 10 +22.19742207447646 + 20 +-23.37894415094994 + 30 +0.0 + 10 +22.19742207447646 + 20 +-23.37894415094994 + 30 +0.0 + 10 +22.19742207447646 + 20 +-23.37894415094994 + 30 +0.0 + 10 +22.60715919734659 + 20 +-23.14532260752844 + 30 +0.0 + 10 +22.60715919734659 + 20 +-23.14532260752844 + 30 +0.0 + 10 +22.60715919734659 + 20 +-23.14532260752844 + 30 +0.0 + 10 +22.6574785611068 + 20 +-23.11656962554199 + 30 +0.0 + 10 +22.6574785611068 + 20 +-23.11656962554199 + 30 +0.0 + 10 +22.67185614895594 + 20 +-23.10578753151112 + 30 +0.0 + 10 +22.68263824298689 + 20 +-23.10219313454882 + 30 +0.0 + 10 +22.69342033701782 + 20 +-23.09500434062423 + 30 +0.0 + 10 +22.71498452507967 + 20 +-23.07703345266873 + 30 +0.0 + 10 +22.73655090685336 + 20 +-23.05906256471321 + 30 +0.0 + 10 +22.75092630099061 + 20 +-23.04109167675773 + 30 +0.0 + 10 +22.78686807690165 + 20 +-23.00155550388445 + 30 +0.0 + 10 +22.80843226496348 + 20 +-22.94764284001796 + 30 +0.0 + 10 +22.81921655270629 + 20 +-22.89373017615142 + 30 +0.0 + 10 +22.82640534663089 + 20 +-22.83981751228493 + 30 +0.0 + 10 +22.82280985281262 + 20 +-22.78231045145612 + 30 +0.0 + 10 +22.80483896485715 + 20 +-22.73558658151422 + 30 +0.0 + 10 +22.79046357071989 + 20 +-22.67808171439734 + 30 +0.0 + 10 +22.75811509491521 + 20 +-22.63135674759943 + 30 +0.0 + 10 +22.71858001889789 + 20 +-22.59541497168842 + 30 +0.0 + 0 +HATCH + 5 +4B +330 +41 +100 +AcDbEntity + 8 +Layer 1 + 62 + 193 +420 + 10185389 +100 +AcDbHatch + 10 +0.0 + 20 +0.0 + 30 +0.0 +210 +0.0 +220 +0.0 +230 +1.0 + 2 +SOLID + 70 + 1 + 71 + 0 + 91 + 1 + 92 + 1 + 93 + 1 + 72 + 4 + 94 + 3 + 73 + 0 + 74 + 1 + 95 + 218 + 96 + 214 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +1.0 + 40 +1.0 + 40 +1.0 + 40 +2.0 + 40 +2.0 + 40 +2.0 + 40 +3.0 + 40 +3.0 + 40 +3.0 + 40 +4.0 + 40 +4.0 + 40 +4.0 + 40 +5.0 + 40 +5.0 + 40 +5.0 + 40 +6.0 + 40 +6.0 + 40 +6.0 + 40 +7.0 + 40 +7.0 + 40 +7.0 + 40 +8.0 + 40 +8.0 + 40 +8.0 + 40 +9.0 + 40 +9.0 + 40 +9.0 + 40 +10.0 + 40 +10.0 + 40 +10.0 + 40 +11.0 + 40 +11.0 + 40 +11.0 + 40 +12.0 + 40 +12.0 + 40 +12.0 + 40 +13.0 + 40 +13.0 + 40 +13.0 + 40 +14.0 + 40 +14.0 + 40 +14.0 + 40 +15.0 + 40 +15.0 + 40 +15.0 + 40 +16.0 + 40 +16.0 + 40 +16.0 + 40 +17.0 + 40 +17.0 + 40 +17.0 + 40 +18.0 + 40 +18.0 + 40 +18.0 + 40 +19.0 + 40 +19.0 + 40 +19.0 + 40 +20.0 + 40 +20.0 + 40 +20.0 + 40 +21.0 + 40 +21.0 + 40 +21.0 + 40 +22.0 + 40 +22.0 + 40 +22.0 + 40 +23.0 + 40 +23.0 + 40 +23.0 + 40 +24.0 + 40 +24.0 + 40 +24.0 + 40 +25.0 + 40 +25.0 + 40 +25.0 + 40 +26.0 + 40 +26.0 + 40 +26.0 + 40 +27.0 + 40 +27.0 + 40 +27.0 + 40 +28.0 + 40 +28.0 + 40 +28.0 + 40 +29.0 + 40 +29.0 + 40 +29.0 + 40 +30.0 + 40 +30.0 + 40 +30.0 + 40 +31.0 + 40 +31.0 + 40 +31.0 + 40 +32.0 + 40 +32.0 + 40 +32.0 + 40 +33.0 + 40 +33.0 + 40 +33.0 + 40 +34.0 + 40 +34.0 + 40 +34.0 + 40 +35.0 + 40 +35.0 + 40 +35.0 + 40 +36.0 + 40 +36.0 + 40 +36.0 + 40 +37.0 + 40 +37.0 + 40 +37.0 + 40 +38.0 + 40 +38.0 + 40 +38.0 + 40 +39.0 + 40 +39.0 + 40 +39.0 + 40 +40.0 + 40 +40.0 + 40 +40.0 + 40 +41.0 + 40 +41.0 + 40 +41.0 + 40 +42.0 + 40 +42.0 + 40 +42.0 + 40 +43.0 + 40 +43.0 + 40 +43.0 + 40 +44.0 + 40 +44.0 + 40 +44.0 + 40 +45.0 + 40 +45.0 + 40 +45.0 + 40 +46.0 + 40 +46.0 + 40 +46.0 + 40 +47.0 + 40 +47.0 + 40 +47.0 + 40 +48.0 + 40 +48.0 + 40 +48.0 + 40 +49.0 + 40 +49.0 + 40 +49.0 + 40 +50.0 + 40 +50.0 + 40 +50.0 + 40 +51.0 + 40 +51.0 + 40 +51.0 + 40 +52.0 + 40 +52.0 + 40 +52.0 + 40 +53.0 + 40 +53.0 + 40 +53.0 + 40 +54.0 + 40 +54.0 + 40 +54.0 + 40 +55.0 + 40 +55.0 + 40 +55.0 + 40 +56.0 + 40 +56.0 + 40 +56.0 + 40 +57.0 + 40 +57.0 + 40 +57.0 + 40 +58.0 + 40 +58.0 + 40 +58.0 + 40 +59.0 + 40 +59.0 + 40 +59.0 + 40 +60.0 + 40 +60.0 + 40 +60.0 + 40 +61.0 + 40 +61.0 + 40 +61.0 + 40 +62.0 + 40 +62.0 + 40 +62.0 + 40 +63.0 + 40 +63.0 + 40 +63.0 + 40 +64.0 + 40 +64.0 + 40 +64.0 + 40 +65.0 + 40 +65.0 + 40 +65.0 + 40 +66.0 + 40 +66.0 + 40 +66.0 + 40 +67.0 + 40 +67.0 + 40 +67.0 + 40 +68.0 + 40 +68.0 + 40 +68.0 + 40 +69.0 + 40 +69.0 + 40 +69.0 + 40 +70.0 + 40 +70.0 + 40 +70.0 + 40 +71.0 + 40 +71.0 + 40 +71.0 + 40 +71.0 + 10 +22.71858001889789 + 20 +-22.59541497168842 + 10 +22.6395076731513 + 20 +-22.52353141986643 + 10 +22.51730695128104 + 20 +-22.49477843788001 + 10 +22.41307492365437 + 20 +-22.52712581682873 + 10 +22.40588612972978 + 20 +-22.52712581682873 + 10 +22.40229282962344 + 20 +-22.53072021379103 + 10 +22.39510403569885 + 20 +-22.53431461075332 + 10 +22.39151073559252 + 20 +-22.53790900771562 + 10 +22.38432194166797 + 20 +-22.53790900771562 + 10 +22.38072644784971 + 20 +-22.53790900771562 + 10 +22.38072644784971 + 20 +-22.53790900771562 + 10 +22.35556676596962 + 20 +-22.55228549870879 + 10 +22.35556676596962 + 20 +-22.55228549870879 + 10 +22.35556676596962 + 20 +-22.55228549870879 + 10 +22.34118917812043 + 20 +-22.55947429263339 + 10 +22.34118917812043 + 20 +-22.55947429263339 + 10 +22.34118917812043 + 20 +-22.55947429263339 + 10 +22.34118917812043 + 20 +-22.55947429263339 + 10 +22.34118917812043 + 20 +-22.55947429263339 + 10 +22.34118917812043 + 20 +-22.55947429263339 + 10 +22.34118917812043 + 20 +-22.55947429263339 + 10 +22.34118917812043 + 20 +-22.55947429263339 + 10 +22.34118917812043 + 20 +-22.55947429263339 + 10 +22.33400038419589 + 20 +-22.56306868959569 + 10 +22.33400038419589 + 20 +-22.56306868959569 + 10 +22.33400038419589 + 20 +-22.56306868959569 + 10 +22.23336385038748 + 20 +-22.62057575042448 + 10 +22.23336385038748 + 20 +-22.62057575042448 + 10 +22.23336385038748 + 20 +-22.62057575042448 + 10 +22.02849528895237 + 20 +-22.7391831721884 + 10 +22.02849528895237 + 20 +-22.7391831721884 + 10 +22.02849528895237 + 20 +-22.7391831721884 + 10 +21.61875816608219 + 20 +-22.9728047156099 + 10 +21.61875816608219 + 20 +-22.9728047156099 + 10 +21.61875816608219 + 20 +-22.9728047156099 + 10 +19.98340736213165 + 20 +-23.91088528625826 + 10 +19.98340736213165 + 20 +-23.91088528625826 + 10 +19.98340736213165 + 20 +-23.91088528625826 + 10 +18.34446216121877 + 20 +-24.84537145994424 + 10 +18.34446216121877 + 20 +-24.84537145994424 + 10 +18.34446216121877 + 20 +-24.84537145994424 + 10 +16.70551696030596 + 20 +-25.7798576336303 + 10 +16.70551696030596 + 20 +-25.7798576336303 + 10 +16.66238639047032 + 20 +-25.80501731551036 + 10 +16.60847372660383 + 20 +-25.79423412462352 + 10 +16.57972074461744 + 20 +-25.75829234871253 + 10 +16.57972074461744 + 20 +-25.75829234871253 + 10 +16.57972074461744 + 20 +-25.75829234871253 + 10 +16.57972074461744 + 20 +-25.75829234871253 + 10 +16.57972074461744 + 20 +-25.75829234871253 + 10 +16.57972074461744 + 20 +-25.75829234871253 + 10 +16.57972074461744 + 20 +-25.75829234871253 + 10 +16.43595364097342 + 20 +-25.58936556318844 + 10 +16.29218653732945 + 20 +-25.42043987452032 + 10 +16.14841943368543 + 20 +-25.24791869203391 + 10 +16.14841943368543 + 20 +-25.24791869203391 + 10 +15.71711812275339 + 20 +-24.73754503535532 + 10 +15.71711812275339 + 20 +-24.73754503535532 + 10 +15.71711812275339 + 20 +-24.73754503535532 + 10 +15.60929279502041 + 20 +-24.60815442270453 + 10 +15.60929279502041 + 20 +-24.60815442270453 + 10 +15.60210400109582 + 20 +-24.5973723286736 + 10 +15.59132190706489 + 20 +-24.58658913778671 + 10 +15.5841331131403 + 20 +-24.57940144071808 + 10 +15.5841331131403 + 20 +-24.57940144071808 + 10 +15.56256782822253 + 20 +-24.55783615580031 + 10 +15.56256782822253 + 20 +-24.55783615580031 + 10 +15.56256782822253 + 20 +-24.55783615580031 + 10 +15.54100254330473 + 20 +-24.53986526784481 + 10 +15.54100254330473 + 20 +-24.53986526784481 + 10 +15.53381374938014 + 20 +-24.53267647392022 + 10 +15.52303165534921 + 20 +-24.52908317381389 + 10 +15.51584286142462 + 20 +-24.52189437988929 + 10 +15.45114810352723 + 20 +-24.47876381005375 + 10 +15.36848136081835 + 20 +-24.46079292209823 + 10 +15.28941011192771 + 20 +-24.46798171602283 + 10 +15.24987393905442 + 20 +-24.47157611298513 + 10 +15.21393216314343 + 20 +-24.4859526039783 + 10 +15.17799038723241 + 20 +-24.50392349193382 + 10 +15.17080159330787 + 20 +-24.50751788889612 + 10 +15.16361389623919 + 20 +-24.51111228585836 + 10 +15.15642510231464 + 20 +-24.51470558596475 + 10 +15.15283070535234 + 20 +-24.51470558596475 + 10 +15.14564300828371 + 20 +-24.52189437988929 + 10 +15.14204861132142 + 20 +-24.52548767999562 + 10 +15.14204861132142 + 20 +-24.52548767999562 + 10 +15.1240777233659 + 20 +-24.53626977402655 + 10 +15.1240777233659 + 20 +-24.53626977402655 + 10 +15.1240777233659 + 20 +-24.53626977402655 + 10 +15.12048332640362 + 20 +-24.53986417098885 + 10 +15.12048332640362 + 20 +-24.53986417098885 + 10 +15.12048332640362 + 20 +-24.53986417098885 + 10 +15.12048332640362 + 20 +-24.53986417098885 + 10 +15.12048332640362 + 20 +-24.53986417098885 + 10 +15.12048332640362 + 20 +-24.53986417098885 + 10 +15.12048332640362 + 20 +-24.53986417098885 + 10 +15.12048332640362 + 20 +-24.53986417098885 + 10 +15.12048332640362 + 20 +-24.53986417098885 + 10 +15.11688892944133 + 20 +-24.54345856795114 + 10 +15.11688892944133 + 20 +-24.54345856795114 + 10 +15.11688892944133 + 20 +-24.54345856795114 + 10 +15.1061068354104 + 20 +-24.55424066198207 + 10 +15.1061068354104 + 20 +-24.55424066198207 + 10 +15.09891804148586 + 20 +-24.56142945590664 + 10 +15.09173034441718 + 20 +-24.56861715297527 + 10 +15.08454155049263 + 20 +-24.57580594689987 + 10 +15.03422328358844 + 20 +-24.63331300772865 + 10 +15.00546920474608 + 20 +-24.70879095651296 + 10 +15.00546920474608 + 20 +-24.7878622054036 + 10 +15.00546920474608 + 20 +-24.82739837827689 + 10 +15.01265799867062 + 20 +-24.86334015418786 + 10 +15.02703448966385 + 20 +-24.89928193009885 + 10 +15.03062888662609 + 20 +-24.91006402412977 + 10 +15.03422328358844 + 20 +-24.91725281805437 + 10 +15.03781658369477 + 20 +-24.92444161197896 + 10 +15.03781658369477 + 20 +-24.92444161197896 + 10 +15.04500537761932 + 20 +-24.93881810297216 + 10 +15.04500537761932 + 20 +-24.93881810297216 + 10 +15.04500537761932 + 20 +-24.93881810297216 + 10 +15.05219417154391 + 20 +-24.94960019700309 + 10 +15.05219417154391 + 20 +-24.94960019700309 + 10 +15.05219417154391 + 20 +-24.94960019700309 + 10 +15.05578856850621 + 20 +-24.96038229103402 + 10 +15.05578856850621 + 20 +-24.96038229103402 + 10 +15.05578856850621 + 20 +-24.96397668799631 + 10 +15.0629773624308 + 20 +-24.97116438506494 + 10 +15.06657066253713 + 20 +-24.97475878202719 + 10 +15.06657066253713 + 20 +-24.97475878202719 + 10 +15.07735275656804 + 20 +-24.99272966998271 + 10 +15.07735275656804 + 20 +-24.99272966998271 + 10 +15.07735275656804 + 20 +-24.99272966998271 + 10 +15.08094715353033 + 20 +-24.99632406694501 + 10 +15.08094715353033 + 20 +-24.99632406694501 + 10 +15.08094715353033 + 20 +-24.99632406694501 + 10 +15.08813594745493 + 20 +-25.00351286086955 + 10 +15.08813594745493 + 20 +-25.00351286086955 + 10 +15.08813594745493 + 20 +-25.00351286086955 + 10 +15.30019220595861 + 20 +-25.2622940861712 + 10 +15.30019220595861 + 20 +-25.2622940861712 + 10 +15.30019220595861 + 20 +-25.2622940861712 + 10 +15.72430472296605 + 20 +-25.77985543991841 + 10 +15.72430472296605 + 20 +-25.77985543991841 + 10 +15.72430472296605 + 20 +-25.77985543991841 + 10 +16.14482393986716 + 20 +-26.29741679366563 + 10 +16.14482393986716 + 20 +-26.29741679366563 + 10 +16.14482393986716 + 20 +-26.29741679366563 + 10 +16.24905487063787 + 20 +-26.42680740631643 + 10 +16.24905487063787 + 20 +-26.42680740631643 + 10 +16.24905487063787 + 20 +-26.42680740631643 + 10 +16.30296753450435 + 20 +-26.49150216421384 + 10 +16.30296753450435 + 20 +-26.49150216421384 + 10 +16.31734402549758 + 20 +-26.50587865520707 + 10 +16.32812721638447 + 20 +-26.52025514620024 + 10 +16.34250370737767 + 20 +-26.53103833708714 + 10 +16.37125668936412 + 20 +-26.55619801896723 + 10 +16.40360516516876 + 20 +-26.57416890692275 + 10 +16.43595254411745 + 20 +-26.58495100095362 + 10 +16.45392343207297 + 20 +-26.59213979487822 + 10 +16.47189432002849 + 20 +-26.59573309498455 + 10 +16.48986520798396 + 20 +-26.59932749194685 + 10 +16.49705400190853 + 20 +-26.59932749194685 + 10 +16.50783609593946 + 20 +-26.60292188890915 + 10 +16.51502488986405 + 20 +-26.60292188890915 + 10 +16.51502488986405 + 20 +-26.60292188890915 + 10 +16.51861928682635 + 20 +-26.60292188890915 + 10 +16.51861928682635 + 20 +-26.60292188890915 + 10 +16.51861928682635 + 20 +-26.60292188890915 + 10 +16.52221368378865 + 20 +-26.60292188890915 + 10 +16.5258080807509 + 20 +-26.60292188890915 + 10 +16.5258080807509 + 20 +-26.60292188890915 + 10 +16.54377896870642 + 20 +-26.60292188890915 + 10 +16.54377896870642 + 20 +-26.60292188890915 + 10 +16.59409723561066 + 20 +-26.60292188890915 + 10 +16.64800989947712 + 20 +-26.58854539791592 + 10 +16.69114046931269 + 20 +-26.56338571603586 + 10 +16.69114046931269 + 20 +-26.56338571603586 + 10 +16.89241463378552 + 20 +-26.44837159437823 + 10 +16.89241463378552 + 20 +-26.44837159437823 + 10 +16.89241463378552 + 20 +-26.44837159437823 + 10 +17.30215065979974 + 20 +-26.21115565399446 + 10 +17.30215065979974 + 20 +-26.21115565399446 + 10 +17.30215065979974 + 20 +-26.21115565399446 + 10 +18.11802941172186 + 20 +-25.74031817018916 + 10 +18.11802941172186 + 20 +-25.74031817018916 + 10 +18.11802941172186 + 20 +-25.74031817018916 + 10 +19.7497858187101 + 20 +-24.79504990247223 + 10 +19.7497858187101 + 20 +-24.79504990247223 + 10 +19.7497858187101 + 20 +-24.79504990247223 + 10 +21.38154332255433 + 20 +-23.84978163475524 + 10 +21.38154332255433 + 20 +-23.84978163475524 + 10 +21.38154332255433 + 20 +-23.84978163475524 + 10 +22.19742207447646 + 20 +-23.37894415094994 + 10 +22.19742207447646 + 20 +-23.37894415094994 + 10 +22.19742207447646 + 20 +-23.37894415094994 + 10 +22.60715919734659 + 20 +-23.14532260752844 + 10 +22.60715919734659 + 20 +-23.14532260752844 + 10 +22.60715919734659 + 20 +-23.14532260752844 + 10 +22.6574785611068 + 20 +-23.11656962554199 + 10 +22.6574785611068 + 20 +-23.11656962554199 + 10 +22.67185614895594 + 20 +-23.10578753151112 + 10 +22.68263824298689 + 20 +-23.10219313454882 + 10 +22.69342033701782 + 20 +-23.09500434062423 + 10 +22.71498452507967 + 20 +-23.07703345266873 + 10 +22.73655090685336 + 20 +-23.05906256471321 + 10 +22.75092630099061 + 20 +-23.04109167675773 + 10 +22.78686807690165 + 20 +-23.00155550388445 + 10 +22.80843226496348 + 20 +-22.94764284001796 + 10 +22.81921655270629 + 20 +-22.89373017615142 + 10 +22.82640534663089 + 20 +-22.83981751228493 + 10 +22.82280985281262 + 20 +-22.78231045145612 + 10 +22.80483896485715 + 20 +-22.73558658151422 + 10 +22.79046357071989 + 20 +-22.67808171439734 + 10 +22.75811509491521 + 20 +-22.63135674759943 + 10 +22.71858001889789 + 20 +-22.59541497168842 + 97 + 0 + 97 + 0 + 75 + 2 + 76 + 1 + 98 + 0 + 0 +ENDBLK + 5 +44 +330 +41 +100 +AcDbEntity + 8 +0 +100 +AcDbBlockEnd + 0 +ENDSEC + 0 +SECTION + 2 +ENTITIES + 0 +INSERT + 5 +42 +330 +1F +100 +AcDbEntity + 8 +Layer 1 +100 +AcDbBlockReference + 2 +block 2 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 0 +ENDSEC + 0 +SECTION + 2 +OBJECTS + 0 +DICTIONARY + 5 +C +330 +0 +100 +AcDbDictionary +281 + 1 + 3 +ACAD_GROUP +350 +D + 3 +ACAD_LAYOUT +350 +1A + 3 +ACAD_MATERIAL +350 +3B + 3 +ACAD_MLEADERSTYLE +350 +4D + 3 +ACAD_MLINESTYLE +350 +17 + 3 +ACAD_PLOTSETTINGS +350 +19 + 3 +ACAD_PLOTSTYLENAME +350 +E + 3 +ACAD_TABLESTYLE +350 +4C + 3 +ACAD_VISUALSTYLE +350 +2A + 0 +SUN + 5 +3F +330 +29 +100 +AcDbSun + 90 + 1 +290 + 0 + 63 + 7 +421 + 16777215 + 40 +1.0 +291 + 1 + 91 + 2455826 + 92 + 54000000 +292 + 0 + 70 + 2 + 71 + 256 +280 + 1 + 0 +DICTIONARY + 5 +D +102 +{ACAD_REACTORS +330 +C +102 +} +330 +C +100 +AcDbDictionary +281 + 1 + 0 +DICTIONARY + 5 +1A +102 +{ACAD_REACTORS +330 +C +102 +} +330 +C +100 +AcDbDictionary +281 + 1 + 3 +Layout1 +350 +1E + 3 +Layout2 +350 +26 + 3 +Model +350 +22 + 0 +DICTIONARY + 5 +3B +102 +{ACAD_REACTORS +330 +C +102 +} +330 +C +100 +AcDbDictionary +281 + 1 + 3 +ByBlock +350 +3D + 3 +ByLayer +350 +3C + 3 +Global +350 +3E + 0 +DICTIONARY + 5 +4D +102 +{ACAD_REACTORS +330 +C +102 +} +330 +C +100 +AcDbDictionary +281 + 1 + 0 +DICTIONARY + 5 +17 +102 +{ACAD_REACTORS +330 +C +102 +} +330 +C +100 +AcDbDictionary +281 + 1 + 3 +Standard +350 +18 + 0 +DICTIONARY + 5 +19 +102 +{ACAD_REACTORS +330 +C +102 +} +330 +C +100 +AcDbDictionary +281 + 1 + 0 +ACDBDICTIONARYWDFLT + 5 +E +102 +{ACAD_REACTORS +330 +C +102 +} +330 +C +100 +AcDbDictionary +281 + 1 + 3 +Normal +350 +F +100 +AcDbDictionaryWithDefault +340 +F + 0 +DICTIONARY + 5 +4C +102 +{ACAD_REACTORS +330 +C +102 +} +330 +C +100 +AcDbDictionary +281 + 1 + 0 +DICTIONARY + 5 +2A +102 +{ACAD_REACTORS +330 +C +102 +} +330 +C +100 +AcDbDictionary +281 + 1 + 3 +2dWireframe +350 +2F + 3 +3D Hidden +350 +31 + 3 +3dWireframe +350 +30 + 3 +Basic +350 +32 + 3 +Brighten +350 +36 + 3 +ColorChange +350 +3A + 3 +Conceptual +350 +34 + 3 +Dim +350 +35 + 3 +Facepattern +350 +39 + 3 +Flat +350 +2B + 3 +FlatWithEdges +350 +2C + 3 +Gouraud +350 +2D + 3 +GouraudWithEdges +350 +2E + 3 +Linepattern +350 +38 + 3 +Realistic +350 +33 + 3 +Thicken +350 +37 + 0 +LAYOUT + 5 +1E +102 +{ACAD_REACTORS +330 +1A +102 +} +330 +1A +100 +AcDbPlotSettings + 1 + + 2 +none_device + 4 + + 6 + + 40 +0.0 + 41 +0.0 + 42 +0.0 + 43 +0.0 + 44 +0.0 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 + 49 +0.0 +140 +0.0 +141 +0.0 +142 +1.0 +143 +1.0 + 70 + 688 + 72 + 0 + 73 + 0 + 74 + 5 + 7 + + 75 + 16 + 76 + 0 + 77 + 2 + 78 + 300 +147 +1.0 +148 +0.0 +149 +0.0 +100 +AcDbLayout + 1 +Layout1 + 70 + 1 + 71 + 1 + 10 +0.0 + 20 +0.0 + 11 +12.0 + 21 +9.0 + 12 +0.0 + 22 +0.0 + 32 +0.0 + 14 +1.000000000000000E+20 + 24 +1.000000000000000E+20 + 34 +1.000000000000000E+20 + 15 +-1.000000000000000E+20 + 25 +-1.000000000000000E+20 + 35 +-1.000000000000000E+20 +146 +0.0 + 13 +0.0 + 23 +0.0 + 33 +0.0 + 16 +1.0 + 26 +0.0 + 36 +0.0 + 17 +0.0 + 27 +1.0 + 37 +0.0 + 76 + 0 +330 +1B + 0 +LAYOUT + 5 +26 +102 +{ACAD_REACTORS +330 +1A +102 +} +330 +1A +100 +AcDbPlotSettings + 1 + + 2 +none_device + 4 + + 6 + + 40 +0.0 + 41 +0.0 + 42 +0.0 + 43 +0.0 + 44 +0.0 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 + 49 +0.0 +140 +0.0 +141 +0.0 +142 +1.0 +143 +1.0 + 70 + 688 + 72 + 0 + 73 + 0 + 74 + 5 + 7 + + 75 + 16 + 76 + 0 + 77 + 2 + 78 + 300 +147 +1.0 +148 +0.0 +149 +0.0 +100 +AcDbLayout + 1 +Layout2 + 70 + 1 + 71 + 2 + 10 +0.0 + 20 +0.0 + 11 +0.0 + 21 +0.0 + 12 +0.0 + 22 +0.0 + 32 +0.0 + 14 +0.0 + 24 +0.0 + 34 +0.0 + 15 +0.0 + 25 +0.0 + 35 +0.0 +146 +0.0 + 13 +0.0 + 23 +0.0 + 33 +0.0 + 16 +1.0 + 26 +0.0 + 36 +0.0 + 17 +0.0 + 27 +1.0 + 37 +0.0 + 76 + 0 +330 +23 + 0 +LAYOUT + 5 +22 +102 +{ACAD_REACTORS +330 +1A +102 +} +330 +1A +100 +AcDbPlotSettings + 1 + + 2 +none_device + 4 + + 6 + + 40 +0.0 + 41 +0.0 + 42 +0.0 + 43 +0.0 + 44 +0.0 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 + 49 +0.0 +140 +0.0 +141 +0.0 +142 +1.0 +143 +1.0 + 70 + 1712 + 72 + 0 + 73 + 0 + 74 + 0 + 7 + + 75 + 0 + 76 + 0 + 77 + 2 + 78 + 300 +147 +1.0 +148 +0.0 +149 +0.0 +100 +AcDbLayout + 1 +Model + 70 + 1 + 71 + 0 + 10 +0.0 + 20 +0.0 + 11 +12.0 + 21 +9.0 + 12 +0.0 + 22 +0.0 + 32 +0.0 + 14 +1.000000000000000E+20 + 24 +1.000000000000000E+20 + 34 +1.000000000000000E+20 + 15 +-1.000000000000000E+20 + 25 +-1.000000000000000E+20 + 35 +-1.000000000000000E+20 +146 +0.0 + 13 +0.0 + 23 +0.0 + 33 +0.0 + 16 +1.0 + 26 +0.0 + 36 +0.0 + 17 +0.0 + 27 +1.0 + 37 +0.0 + 76 + 0 +330 +1F +331 +29 + 0 +MATERIAL + 5 +3D +102 +{ACAD_REACTORS +330 +3B +102 +} +330 +3B +100 +AcDbMaterial + 1 +ByBlock + 72 + 1 + 94 + 127 + 0 +MATERIAL + 5 +3C +102 +{ACAD_REACTORS +330 +3B +102 +} +330 +3B +100 +AcDbMaterial + 1 +ByLayer + 72 + 1 + 94 + 127 + 0 +MATERIAL + 5 +3E +102 +{ACAD_REACTORS +330 +3B +102 +} +330 +3B +100 +AcDbMaterial + 1 +Global + 72 + 1 + 94 + 127 + 0 +MLINESTYLE + 5 +18 +102 +{ACAD_REACTORS +330 +17 +102 +} +330 +17 +100 +AcDbMlineStyle + 2 +Standard + 70 + 0 + 3 + + 62 + 256 + 51 +90.0 + 52 +90.0 + 71 + 2 + 49 +0.5 + 62 + 256 + 6 +BYLAYER + 49 +-0.5 + 62 + 256 + 6 +BYLAYER + 0 +ACDBPLACEHOLDER + 5 +F +102 +{ACAD_REACTORS +330 +E +102 +} +330 +E + 0 +VISUALSTYLE + 5 +2F +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +2dWireframe + 70 + 4 +177 + 2 +291 + 0 + 71 + 0 +176 + 1 + 72 + 2 +176 + 1 + 73 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 1 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 0 +176 + 1 + 66 + 257 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 1 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +31 +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +3D Hidden + 70 + 6 +177 + 2 +291 + 0 + 71 + 1 +176 + 1 + 72 + 2 +176 + 1 + 73 + 2 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 2 +176 + 1 + 91 + 2 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 2 +176 + 1 +175 + 1 +176 + 1 + 42 +40.0 +176 + 1 + 92 + 0 +176 + 1 + 66 + 257 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 3 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 1 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +30 +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +3dWireframe + 70 + 5 +177 + 2 +291 + 0 + 71 + 0 +176 + 1 + 72 + 2 +176 + 1 + 73 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 1 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 0 +176 + 1 + 66 + 257 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 1 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +32 +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +Basic + 70 + 7 +177 + 2 +291 + 1 + 71 + 1 +176 + 1 + 72 + 0 +176 + 1 + 73 + 1 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 0 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 8 +176 + 1 + 66 + 7 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 1 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +36 +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +Brighten + 70 + 12 +177 + 2 +291 + 1 + 71 + 2 +176 + 1 + 72 + 2 +176 + 1 + 73 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 1 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 8 +176 + 1 + 66 + 7 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 1 +176 + 1 + 44 +50.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +3A +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +ColorChange + 70 + 16 +177 + 2 +291 + 1 + 71 + 2 +176 + 1 + 72 + 2 +176 + 1 + 73 + 3 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 8 +421 + 8421504 +176 + 1 + 74 + 1 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 8 +176 + 1 + 66 + 8 +424 + 8421504 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 1 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +34 +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +Conceptual + 70 + 9 +177 + 2 +291 + 0 + 71 + 3 +176 + 1 + 72 + 2 +176 + 1 + 73 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 2 +176 + 1 + 91 + 2 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +40.0 +176 + 1 + 92 + 8 +176 + 1 + 66 + 7 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 3 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 1 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +35 +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +Dim + 70 + 11 +177 + 2 +291 + 1 + 71 + 2 +176 + 1 + 72 + 2 +176 + 1 + 73 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 1 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 8 +176 + 1 + 66 + 7 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 1 +176 + 1 + 44 +-50.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +39 +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +Facepattern + 70 + 15 +177 + 2 +291 + 1 + 71 + 2 +176 + 1 + 72 + 2 +176 + 1 + 73 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 1 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 8 +176 + 1 + 66 + 7 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 1 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +2B +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +Flat + 70 + 0 +177 + 2 +291 + 1 + 71 + 2 +176 + 1 + 72 + 1 +176 + 1 + 73 + 1 +176 + 1 + 90 + 2 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 0 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 8 +176 + 1 + 66 + 7 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 13 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +2C +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +FlatWithEdges + 70 + 1 +177 + 2 +291 + 1 + 71 + 2 +176 + 1 + 72 + 1 +176 + 1 + 73 + 1 +176 + 1 + 90 + 2 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 1 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 0 +176 + 1 + 66 + 257 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 13 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +2D +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +Gouraud + 70 + 2 +177 + 2 +291 + 1 + 71 + 2 +176 + 1 + 72 + 2 +176 + 1 + 73 + 1 +176 + 1 + 90 + 2 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 0 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 0 +176 + 1 + 66 + 7 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 13 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +2E +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +GouraudWithEdges + 70 + 3 +177 + 2 +291 + 1 + 71 + 2 +176 + 1 + 72 + 2 +176 + 1 + 73 + 1 +176 + 1 + 90 + 2 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 1 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 0 +176 + 1 + 66 + 257 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 13 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +38 +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +Linepattern + 70 + 14 +177 + 2 +291 + 1 + 71 + 2 +176 + 1 + 72 + 2 +176 + 1 + 73 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 1 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 7 +176 + 1 +175 + 7 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 8 +176 + 1 + 66 + 7 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 1 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +33 +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +Realistic + 70 + 8 +177 + 2 +291 + 0 + 71 + 2 +176 + 1 + 72 + 2 +176 + 1 + 73 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 1 +176 + 1 + 91 + 0 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 8 +176 + 1 + 66 + 8 +424 + 7895160 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 13 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +37 +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +Thicken + 70 + 13 +177 + 2 +291 + 1 + 71 + 2 +176 + 1 + 72 + 2 +176 + 1 + 73 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 1 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 12 +176 + 1 + 66 + 7 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 1 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +ENDSEC + 0 +EOF diff --git a/Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 01.eps b/Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 01.eps new file mode 100644 index 0000000..55cfc0c Binary files /dev/null and b/Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 01.eps differ diff --git a/Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 01.png b/Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 01.png new file mode 100644 index 0000000..276fbd2 Binary files /dev/null and b/Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 01.png differ diff --git a/Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 01.svg b/Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 01.svg new file mode 100644 index 0000000..aaaed3e --- /dev/null +++ b/Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 01.svg @@ -0,0 +1,56 @@ + + + + + + + + + + diff --git a/Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 02.dxf b/Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 02.dxf new file mode 100644 index 0000000..a032c8b --- /dev/null +++ b/Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 02.dxf @@ -0,0 +1,19050 @@ + 0 +SECTION + 2 +HEADER + 9 +$ACADVER + 1 +AC1024 + 9 +$ACADMAINTVER + 70 + 6 + 9 +$DWGCODEPAGE + 3 +ANSI_1252 + 9 +$INSBASE + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$EXTMIN + 10 +1.000000000000000E+20 + 20 +1.000000000000000E+20 + 30 +1.000000000000000E+20 + 9 +$EXTMAX + 10 +-1.000000000000000E+20 + 20 +-1.000000000000000E+20 + 30 +-1.000000000000000E+20 + 9 +$LIMMIN + 10 +0.0 + 20 +0.0 + 9 +$LIMMAX + 10 +12.0 + 20 +9.0 + 9 +$ORTHOMODE + 70 + 0 + 9 +$REGENMODE + 70 + 1 + 9 +$FILLMODE + 70 + 1 + 9 +$QTEXTMODE + 70 + 0 + 9 +$MIRRTEXT + 70 + 1 + 9 +$LTSCALE + 40 +1.0 + 9 +$ATTMODE + 70 + 1 + 9 +$TEXTSIZE + 40 +0.2 + 9 +$TRACEWID + 40 +0.05 + 9 +$TEXTSTYLE + 7 +Standard + 9 +$CLAYER + 8 +0 + 9 +$CELTYPE + 6 +ByLayer + 9 +$CECOLOR + 62 + 256 + 9 +$CELTSCALE + 40 +1.0 + 9 +$DISPSILH + 70 + 0 + 9 +$DIMSCALE + 40 +1.0 + 9 +$DIMASZ + 40 +0.18 + 9 +$DIMEXO + 40 +0.0625 + 9 +$DIMDLI + 40 +0.38 + 9 +$DIMRND + 40 +0.0 + 9 +$DIMDLE + 40 +0.0 + 9 +$DIMEXE + 40 +0.18 + 9 +$DIMTP + 40 +0.0 + 9 +$DIMTM + 40 +0.0 + 9 +$DIMTXT + 40 +0.18 + 9 +$DIMCEN + 40 +0.09 + 9 +$DIMTSZ + 40 +0.0 + 9 +$DIMTOL + 70 + 0 + 9 +$DIMLIM + 70 + 0 + 9 +$DIMTIH + 70 + 1 + 9 +$DIMTOH + 70 + 1 + 9 +$DIMSE1 + 70 + 0 + 9 +$DIMSE2 + 70 + 0 + 9 +$DIMTAD + 70 + 0 + 9 +$DIMZIN + 70 + 0 + 9 +$DIMBLK + 1 + + 9 +$DIMASO + 70 + 1 + 9 +$DIMSHO + 70 + 1 + 9 +$DIMPOST + 1 + + 9 +$DIMAPOST + 1 + + 9 +$DIMALT + 70 + 0 + 9 +$DIMALTD + 70 + 2 + 9 +$DIMALTF + 40 +25.4 + 9 +$DIMLFAC + 40 +1.0 + 9 +$DIMTOFL + 70 + 0 + 9 +$DIMTVP + 40 +0.0 + 9 +$DIMTIX + 70 + 0 + 9 +$DIMSOXD + 70 + 0 + 9 +$DIMSAH + 70 + 0 + 9 +$DIMBLK1 + 1 + + 9 +$DIMBLK2 + 1 + + 9 +$DIMSTYLE + 2 +Standard + 9 +$DIMCLRD + 70 + 0 + 9 +$DIMCLRE + 70 + 0 + 9 +$DIMCLRT + 70 + 0 + 9 +$DIMTFAC + 40 +1.0 + 9 +$DIMGAP + 40 +0.09 + 9 +$DIMJUST + 70 + 0 + 9 +$DIMSD1 + 70 + 0 + 9 +$DIMSD2 + 70 + 0 + 9 +$DIMTOLJ + 70 + 1 + 9 +$DIMTZIN + 70 + 0 + 9 +$DIMALTZ + 70 + 0 + 9 +$DIMALTTZ + 70 + 0 + 9 +$DIMUPT + 70 + 0 + 9 +$DIMDEC + 70 + 4 + 9 +$DIMTDEC + 70 + 4 + 9 +$DIMALTU + 70 + 2 + 9 +$DIMALTTD + 70 + 2 + 9 +$DIMTXSTY + 7 +Standard + 9 +$DIMAUNIT + 70 + 0 + 9 +$DIMADEC + 70 + 0 + 9 +$DIMALTRND + 40 +0.0 + 9 +$DIMAZIN + 70 + 0 + 9 +$DIMDSEP + 70 + 46 + 9 +$DIMATFIT + 70 + 3 + 9 +$DIMFRAC + 70 + 0 + 9 +$DIMLDRBLK + 1 + + 9 +$DIMLUNIT + 70 + 2 + 9 +$DIMLWD + 70 + -2 + 9 +$DIMLWE + 70 + -2 + 9 +$DIMTMOVE + 70 + 0 + 9 +$DIMFXL + 40 +1.0 + 9 +$DIMFXLON + 70 + 0 + 9 +$DIMJOGANG + 40 +0.7853981633974483 + 9 +$DIMTFILL + 70 + 0 + 9 +$DIMTFILLCLR + 70 + 0 + 9 +$DIMARCSYM + 70 + 0 + 9 +$DIMLTYPE + 6 + + 9 +$DIMLTEX1 + 6 + + 9 +$DIMLTEX2 + 6 + + 9 +$DIMTXTDIRECTION + 70 + 0 + 9 +$LUNITS + 70 + 2 + 9 +$LUPREC + 70 + 4 + 9 +$SKETCHINC + 40 +0.1 + 9 +$FILLETRAD + 40 +0.5 + 9 +$AUNITS + 70 + 0 + 9 +$AUPREC + 70 + 0 + 9 +$MENU + 1 +. + 9 +$ELEVATION + 40 +0.0 + 9 +$PELEVATION + 40 +0.0 + 9 +$THICKNESS + 40 +0.0 + 9 +$LIMCHECK + 70 + 0 + 9 +$CHAMFERA + 40 +0.5 + 9 +$CHAMFERB + 40 +0.5 + 9 +$CHAMFERC + 40 +1.0 + 9 +$CHAMFERD + 40 +0.0 + 9 +$SKPOLY + 70 + 0 + 9 +$TDCREATE + 40 +2459543.541501424 + 9 +$TDUCREATE + 40 +2459543.291501424 + 9 +$TDUPDATE + 40 +2459543.541501447 + 9 +$TDUUPDATE + 40 +2459543.291501447 + 9 +$TDINDWG + 40 +0.0000000116 + 9 +$TDUSRTIMER + 40 +0.0000000116 + 9 +$USRTIMER + 70 + 1 + 9 +$ANGBASE + 50 +0.0 + 9 +$ANGDIR + 70 + 0 + 9 +$PDMODE + 70 + 0 + 9 +$PDSIZE + 40 +0.0 + 9 +$PLINEWID + 40 +0.0 + 9 +$SPLFRAME + 70 + 0 + 9 +$SPLINETYPE + 70 + 6 + 9 +$SPLINESEGS + 70 + 8 + 9 +$HANDSEED + 5 +48 + 9 +$SURFTAB1 + 70 + 6 + 9 +$SURFTAB2 + 70 + 6 + 9 +$SURFTYPE + 70 + 6 + 9 +$SURFU + 70 + 6 + 9 +$SURFV + 70 + 6 + 9 +$UCSBASE + 2 + + 9 +$UCSNAME + 2 + + 9 +$UCSORG + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSXDIR + 10 +1.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSYDIR + 10 +0.0 + 20 +1.0 + 30 +0.0 + 9 +$UCSORTHOREF + 2 + + 9 +$UCSORTHOVIEW + 70 + 0 + 9 +$UCSORGTOP + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGBOTTOM + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGLEFT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGRIGHT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGFRONT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGBACK + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSBASE + 2 + + 9 +$PUCSNAME + 2 + + 9 +$PUCSORG + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSXDIR + 10 +1.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSYDIR + 10 +0.0 + 20 +1.0 + 30 +0.0 + 9 +$PUCSORTHOREF + 2 + + 9 +$PUCSORTHOVIEW + 70 + 0 + 9 +$PUCSORGTOP + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGBOTTOM + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGLEFT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGRIGHT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGFRONT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGBACK + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$USERI1 + 70 + 0 + 9 +$USERI2 + 70 + 0 + 9 +$USERI3 + 70 + 0 + 9 +$USERI4 + 70 + 0 + 9 +$USERI5 + 70 + 0 + 9 +$USERR1 + 40 +0.0 + 9 +$USERR2 + 40 +0.0 + 9 +$USERR3 + 40 +0.0 + 9 +$USERR4 + 40 +0.0 + 9 +$USERR5 + 40 +0.0 + 9 +$WORLDVIEW + 70 + 1 + 9 +$SHADEDGE + 70 + 3 + 9 +$SHADEDIF + 70 + 70 + 9 +$TILEMODE + 70 + 1 + 9 +$MAXACTVP + 70 + 64 + 9 +$PINSBASE + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PLIMCHECK + 70 + 0 + 9 +$PEXTMIN + 10 +1.000000000000000E+20 + 20 +1.000000000000000E+20 + 30 +1.000000000000000E+20 + 9 +$PEXTMAX + 10 +-1.000000000000000E+20 + 20 +-1.000000000000000E+20 + 30 +-1.000000000000000E+20 + 9 +$PLIMMIN + 10 +0.0 + 20 +0.0 + 9 +$PLIMMAX + 10 +12.0 + 20 +9.0 + 9 +$UNITMODE + 70 + 0 + 9 +$VISRETAIN + 70 + 1 + 9 +$PLINEGEN + 70 + 0 + 9 +$PSLTSCALE + 70 + 1 + 9 +$TREEDEPTH + 70 + 3020 + 9 +$CMLSTYLE + 2 +Standard + 9 +$CMLJUST + 70 + 0 + 9 +$CMLSCALE + 40 +1.0 + 9 +$PROXYGRAPHICS + 70 + 1 + 9 +$MEASUREMENT + 70 + 0 + 9 +$CELWEIGHT +370 + -1 + 9 +$ENDCAPS +280 + 0 + 9 +$JOINSTYLE +280 + 0 + 9 +$LWDISPLAY +290 + 1 + 9 +$INSUNITS + 70 + 1 + 9 +$HYPERLINKBASE + 1 + + 9 +$STYLESHEET + 1 + + 9 +$XEDIT +290 + 1 + 9 +$CEPSNTYPE +380 + 0 + 9 +$PSTYLEMODE +290 + 1 + 9 +$FINGERPRINTGUID + 2 +{7B41811E-9A88-4728-BD35-F77767BF3CF4} + 9 +$VERSIONGUID + 2 +{FAEB1C32-E019-11D5-929B-00C0DF256EC4} + 9 +$EXTNAMES +290 + 1 + 9 +$PSVPSCALE + 40 +0.0 + 9 +$OLESTARTUP +290 + 0 + 9 +$SORTENTS +280 + 127 + 9 +$INDEXCTL +280 + 0 + 9 +$HIDETEXT +280 + 1 + 9 +$XCLIPFRAME +280 + 2 + 9 +$HALOGAP +280 + 0 + 9 +$OBSCOLOR + 70 + 257 + 9 +$OBSLTYPE +280 + 0 + 9 +$INTERSECTIONDISPLAY +280 + 0 + 9 +$INTERSECTIONCOLOR + 70 + 257 + 9 +$DIMASSOC +280 + 2 + 9 +$PROJECTNAME + 1 + + 9 +$CAMERADISPLAY +290 + 0 + 9 +$LENSLENGTH + 40 +50.0 + 9 +$CAMERAHEIGHT + 40 +0.0 + 9 +$STEPSPERSEC + 40 +2.0 + 9 +$STEPSIZE + 40 +6.0 + 9 +$3DDWFPREC + 40 +2.0 + 9 +$PSOLWIDTH + 40 +0.25 + 9 +$PSOLHEIGHT + 40 +4.0 + 9 +$LOFTANG1 + 40 +1.570796326794897 + 9 +$LOFTANG2 + 40 +1.570796326794897 + 9 +$LOFTMAG1 + 40 +0.0 + 9 +$LOFTMAG2 + 40 +0.0 + 9 +$LOFTPARAM + 70 + 7 + 9 +$LOFTNORMALS +280 + 1 + 9 +$LATITUDE + 40 +37.795 + 9 +$LONGITUDE + 40 +-122.394 + 9 +$NORTHDIRECTION + 40 +0.0 + 9 +$TIMEZONE + 70 + -8000 + 9 +$LIGHTGLYPHDISPLAY +280 + 1 + 9 +$TILEMODELIGHTSYNCH +280 + 1 + 9 +$CMATERIAL +347 +3C + 9 +$SOLIDHIST +280 + 1 + 9 +$SHOWHIST +280 + 1 + 9 +$DWFFRAME +280 + 2 + 9 +$DGNFRAME +280 + 2 + 9 +$REALWORLDSCALE +290 + 1 + 9 +$INTERFERECOLOR + 62 + 256 + 9 +$CSHADOW +280 + 0 + 9 +$SHADOWPLANELOCATION + 40 +0.0 + 0 +ENDSEC + 0 +SECTION + 2 +CLASSES + 0 +CLASS + 1 +ACDBDICTIONARYWDFLT + 2 +AcDbDictionaryWithDefault + 3 +ObjectDBX Classes + 90 + 0 + 91 + 4 +280 + 0 +281 + 0 + 0 +CLASS + 1 +VISUALSTYLE + 2 +AcDbVisualStyle + 3 +ObjectDBX Classes + 90 + 4095 + 91 + 4 +280 + 0 +281 + 0 + 0 +CLASS + 1 +MATERIAL + 2 +AcDbMaterial + 3 +ObjectDBX Classes + 90 + 1153 + 91 + 4 +280 + 0 +281 + 0 + 0 +CLASS + 1 +SUN + 2 +AcDbSun + 3 +SCENEOE + 90 + 1024 + 91 + 4 +280 + 0 +281 + 0 + 0 +ENDSEC + 0 +SECTION + 2 +TABLES + 0 +TABLE + 2 +VPORT + 5 +8 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +VPORT + 5 +29 +330 +8 +100 +AcDbSymbolTableRecord +100 +AcDbViewportTableRecord + 2 +*Active + 70 + 0 + 10 +0.0 + 20 +0.0 + 11 +1.0 + 21 +1.0 + 12 +0.0 + 22 +0.0 + 13 +0.0 + 23 +0.0 + 14 +0.5 + 24 +0.5 + 15 +0.5 + 25 +0.5 + 16 +0.0 + 26 +0.0 + 36 +1.0 + 17 +18.82499715660788 + 27 +-25.16221337917763 + 37 +0.0 + 40 +7.926041404099376 + 41 +1.261663860956873 + 42 +50.0 + 43 +0.0 + 44 +0.0 + 50 +0.0 + 51 +0.0 + 71 + 0 + 72 + 100 + 73 + 1 + 74 + 3 + 75 + 0 + 76 + 0 + 77 + 0 + 78 + 0 +281 + 0 + 65 + 1 +110 +0.0 +120 +0.0 +130 +0.0 +111 +1.0 +121 +0.0 +131 +0.0 +112 +0.0 +122 +1.0 +132 +0.0 + 79 + 0 +146 +0.0 + 60 + 3 + 61 + 5 +292 + 1 +282 + 1 +141 +0.0 +142 +0.0 + 63 + 250 +361 +3F + 0 +ENDTAB + 0 +TABLE + 2 +LTYPE + 5 +5 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +LTYPE + 5 +14 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +ByBlock + 70 + 0 + 3 + + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +LTYPE + 5 +15 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +ByLayer + 70 + 0 + 3 + + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +LTYPE + 5 +16 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +Continuous + 70 + 0 + 3 +Solid line + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +ENDTAB + 0 +TABLE + 2 +LAYER + 5 +2 +330 +0 +100 +AcDbSymbolTable + 70 + 2 + 0 +LAYER + 5 +10 +330 +2 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +0 + 70 + 0 + 62 + 7 + 6 +Continuous +370 + -3 +390 +F +347 +3E + 0 +LAYER + 5 +40 +330 +2 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +Layer 1 + 70 + 0 + 62 + 7 + 6 +Continuous +370 + -3 +390 +F +347 +3E + 0 +ENDTAB + 0 +TABLE + 2 +STYLE + 5 +3 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +STYLE + 5 +11 +330 +3 +100 +AcDbSymbolTableRecord +100 +AcDbTextStyleTableRecord + 2 +Standard + 70 + 0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 + 0 + 42 +0.2 + 3 +txt + 4 + + 0 +ENDTAB + 0 +TABLE + 2 +VIEW + 5 +6 +330 +0 +100 +AcDbSymbolTable + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +UCS + 5 +7 +330 +0 +100 +AcDbSymbolTable + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +APPID + 5 +9 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +APPID + 5 +12 +330 +9 +100 +AcDbSymbolTableRecord +100 +AcDbRegAppTableRecord + 2 +ACAD + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +DIMSTYLE + 5 +A +330 +0 +100 +AcDbSymbolTable + 70 + 1 +100 +AcDbDimStyleTable + 0 +DIMSTYLE +105 +27 +330 +A +100 +AcDbSymbolTableRecord +100 +AcDbDimStyleTableRecord + 2 +Standard + 70 + 0 +178 + 0 +340 +11 + 0 +ENDTAB + 0 +TABLE + 2 +BLOCK_RECORD + 5 +1 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +BLOCK_RECORD + 5 +1F +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*Model_Space +340 +22 + 70 + 0 +280 + 1 +281 + 0 + 0 +BLOCK_RECORD + 5 +1B +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*Paper_Space +340 +1E + 70 + 0 +280 + 1 +281 + 0 + 0 +BLOCK_RECORD + 5 +23 +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*Paper_Space0 +340 +26 + 70 + 0 +280 + 1 +281 + 0 + 0 +ENDTAB + 0 +ENDSEC + 0 +SECTION + 2 +BLOCKS + 0 +BLOCK + 5 +20 +330 +1F +100 +AcDbEntity + 8 +0 +100 +AcDbBlockBegin + 2 +*Model_Space + 70 + 0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +*Model_Space + 1 + + 0 +ENDBLK + 5 +21 +330 +1F +100 +AcDbEntity + 8 +0 +100 +AcDbBlockEnd + 0 +BLOCK + 5 +1C +330 +1B +100 +AcDbEntity + 67 + 1 + 8 +0 +100 +AcDbBlockBegin + 2 +*Paper_Space + 70 + 0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +*Paper_Space + 1 + + 0 +ENDBLK + 5 +1D +330 +1B +100 +AcDbEntity + 67 + 1 + 8 +0 +100 +AcDbBlockEnd + 0 +BLOCK + 5 +24 +330 +23 +100 +AcDbEntity + 8 +0 +100 +AcDbBlockBegin + 2 +*Paper_Space0 + 70 + 0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +*Paper_Space0 + 1 + + 0 +ENDBLK + 5 +25 +330 +23 +100 +AcDbEntity + 8 +0 +100 +AcDbBlockEnd + 0 +ENDSEC + 0 +SECTION + 2 +ENTITIES + 0 +SPLINE + 5 +41 +330 +1F +100 +AcDbEntity + 8 +Layer 1 +370 + 0 +100 +AcDbSpline +210 +0.0 +220 +0.0 +230 +1.0 + 70 + 11 + 71 + 3 + 72 + 287 + 73 + 283 + 74 + 0 + 42 +0.0000000001 + 43 +0.0000000001 + 12 +1.0 + 22 +0.0 + 32 +0.0 + 13 +1.0 + 23 +0.0 + 33 +0.0 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +1.0 + 40 +1.0 + 40 +1.0 + 40 +2.0 + 40 +2.0 + 40 +2.0 + 40 +3.0 + 40 +3.0 + 40 +3.0 + 40 +4.0 + 40 +4.0 + 40 +4.0 + 40 +5.0 + 40 +5.0 + 40 +5.0 + 40 +6.0 + 40 +6.0 + 40 +6.0 + 40 +7.0 + 40 +7.0 + 40 +7.0 + 40 +8.0 + 40 +8.0 + 40 +8.0 + 40 +9.0 + 40 +9.0 + 40 +9.0 + 40 +10.0 + 40 +10.0 + 40 +10.0 + 40 +11.0 + 40 +11.0 + 40 +11.0 + 40 +12.0 + 40 +12.0 + 40 +12.0 + 40 +13.0 + 40 +13.0 + 40 +13.0 + 40 +14.0 + 40 +14.0 + 40 +14.0 + 40 +15.0 + 40 +15.0 + 40 +15.0 + 40 +16.0 + 40 +16.0 + 40 +16.0 + 40 +17.0 + 40 +17.0 + 40 +17.0 + 40 +18.0 + 40 +18.0 + 40 +18.0 + 40 +19.0 + 40 +19.0 + 40 +19.0 + 40 +20.0 + 40 +20.0 + 40 +20.0 + 40 +21.0 + 40 +21.0 + 40 +21.0 + 40 +22.0 + 40 +22.0 + 40 +22.0 + 40 +23.0 + 40 +23.0 + 40 +23.0 + 40 +24.0 + 40 +24.0 + 40 +24.0 + 40 +25.0 + 40 +25.0 + 40 +25.0 + 40 +26.0 + 40 +26.0 + 40 +26.0 + 40 +27.0 + 40 +27.0 + 40 +27.0 + 40 +28.0 + 40 +28.0 + 40 +28.0 + 40 +29.0 + 40 +29.0 + 40 +29.0 + 40 +30.0 + 40 +30.0 + 40 +30.0 + 40 +31.0 + 40 +31.0 + 40 +31.0 + 40 +32.0 + 40 +32.0 + 40 +32.0 + 40 +33.0 + 40 +33.0 + 40 +33.0 + 40 +34.0 + 40 +34.0 + 40 +34.0 + 40 +35.0 + 40 +35.0 + 40 +35.0 + 40 +36.0 + 40 +36.0 + 40 +36.0 + 40 +37.0 + 40 +37.0 + 40 +37.0 + 40 +38.0 + 40 +38.0 + 40 +38.0 + 40 +39.0 + 40 +39.0 + 40 +39.0 + 40 +40.0 + 40 +40.0 + 40 +40.0 + 40 +41.0 + 40 +41.0 + 40 +41.0 + 40 +42.0 + 40 +42.0 + 40 +42.0 + 40 +43.0 + 40 +43.0 + 40 +43.0 + 40 +44.0 + 40 +44.0 + 40 +44.0 + 40 +45.0 + 40 +45.0 + 40 +45.0 + 40 +46.0 + 40 +46.0 + 40 +46.0 + 40 +47.0 + 40 +47.0 + 40 +47.0 + 40 +48.0 + 40 +48.0 + 40 +48.0 + 40 +49.0 + 40 +49.0 + 40 +49.0 + 40 +50.0 + 40 +50.0 + 40 +50.0 + 40 +51.0 + 40 +51.0 + 40 +51.0 + 40 +52.0 + 40 +52.0 + 40 +52.0 + 40 +53.0 + 40 +53.0 + 40 +53.0 + 40 +54.0 + 40 +54.0 + 40 +54.0 + 40 +55.0 + 40 +55.0 + 40 +55.0 + 40 +56.0 + 40 +56.0 + 40 +56.0 + 40 +57.0 + 40 +57.0 + 40 +57.0 + 40 +58.0 + 40 +58.0 + 40 +58.0 + 40 +59.0 + 40 +59.0 + 40 +59.0 + 40 +60.0 + 40 +60.0 + 40 +60.0 + 40 +61.0 + 40 +61.0 + 40 +61.0 + 40 +62.0 + 40 +62.0 + 40 +62.0 + 40 +63.0 + 40 +63.0 + 40 +63.0 + 40 +64.0 + 40 +64.0 + 40 +64.0 + 40 +65.0 + 40 +65.0 + 40 +65.0 + 40 +66.0 + 40 +66.0 + 40 +66.0 + 40 +67.0 + 40 +67.0 + 40 +67.0 + 40 +68.0 + 40 +68.0 + 40 +68.0 + 40 +69.0 + 40 +69.0 + 40 +69.0 + 40 +70.0 + 40 +70.0 + 40 +70.0 + 40 +71.0 + 40 +71.0 + 40 +71.0 + 40 +72.0 + 40 +72.0 + 40 +72.0 + 40 +73.0 + 40 +73.0 + 40 +73.0 + 40 +74.0 + 40 +74.0 + 40 +74.0 + 40 +75.0 + 40 +75.0 + 40 +75.0 + 40 +76.0 + 40 +76.0 + 40 +76.0 + 40 +77.0 + 40 +77.0 + 40 +77.0 + 40 +78.0 + 40 +78.0 + 40 +78.0 + 40 +79.0 + 40 +79.0 + 40 +79.0 + 40 +80.0 + 40 +80.0 + 40 +80.0 + 40 +81.0 + 40 +81.0 + 40 +81.0 + 40 +82.0 + 40 +82.0 + 40 +82.0 + 40 +83.0 + 40 +83.0 + 40 +83.0 + 40 +84.0 + 40 +84.0 + 40 +84.0 + 40 +85.0 + 40 +85.0 + 40 +85.0 + 40 +86.0 + 40 +86.0 + 40 +86.0 + 40 +87.0 + 40 +87.0 + 40 +87.0 + 40 +88.0 + 40 +88.0 + 40 +88.0 + 40 +89.0 + 40 +89.0 + 40 +89.0 + 40 +90.0 + 40 +90.0 + 40 +90.0 + 40 +91.0 + 40 +91.0 + 40 +91.0 + 40 +92.0 + 40 +92.0 + 40 +92.0 + 40 +93.0 + 40 +93.0 + 40 +93.0 + 40 +94.0 + 40 +94.0 + 40 +94.0 + 40 +94.0 + 10 +23.78464640080316 + 20 +-22.86631945452912 + 30 +0.0 + 10 +23.78464640080316 + 20 +-22.86631945452912 + 30 +0.0 + 10 +23.76668166432758 + 20 +-22.82679681498666 + 30 +0.0 + 10 +23.76668166432758 + 20 +-22.82679681498666 + 30 +0.0 + 10 +23.76668166432758 + 20 +-22.82679681498666 + 30 +0.0 + 10 +23.76308959421688 + 20 +-22.81601841169347 + 30 +0.0 + 10 +23.76308959421688 + 20 +-22.81601841169347 + 30 +0.0 + 10 +23.76308959421688 + 20 +-22.81601841169347 + 30 +0.0 + 10 +23.75949752410613 + 20 +-22.80883207851106 + 30 +0.0 + 10 +23.75949752410613 + 20 +-22.80883207851106 + 30 +0.0 + 10 +23.75949752410613 + 20 +-22.80883207851106 + 30 +0.0 + 10 +23.75231119092369 + 20 +-22.79446050862665 + 30 +0.0 + 10 +23.75231119092369 + 20 +-22.79446050862665 + 30 +0.0 + 10 +23.75231119092369 + 20 +-22.79446050862665 + 30 +0.0 + 10 +23.74512485774122 + 20 +-22.78008893874228 + 30 +0.0 + 10 +23.74512485774122 + 20 +-22.78008893874228 + 30 +0.0 + 10 +23.74512485774122 + 20 +-22.78008893874228 + 30 +0.0 + 10 +23.7415327876305 + 20 +-22.77290260555982 + 30 +0.0 + 10 +23.7415327876305 + 20 +-22.77290260555982 + 30 +0.0 + 10 +23.7415327876305 + 20 +-22.77290260555982 + 30 +0.0 + 10 +23.7415327876305 + 20 +-22.76930943896857 + 30 +0.0 + 10 +23.7415327876305 + 20 +-22.76930943896857 + 30 +0.0 + 10 +23.73434645444806 + 20 +-22.75493786908419 + 30 +0.0 + 10 +23.73794071751978 + 20 +-22.76212310578613 + 30 +0.0 + 10 +23.73434645444806 + 20 +-22.75853103567541 + 30 +0.0 + 10 +23.73434645444806 + 20 +-22.75853103567541 + 30 +0.0 + 10 +23.71278964786168 + 20 +-22.72619472931539 + 30 +0.0 + 10 +23.71278964786168 + 20 +-22.72619472931539 + 30 +0.0 + 10 +23.69841698149677 + 20 +-22.70463682624856 + 30 +0.0 + 10 +23.68045224502121 + 20 +-22.68667208977291 + 30 +0.0 + 10 +23.6660817716173 + 20 +-22.66511418670608 + 30 +0.0 + 10 +23.64811703514164 + 20 +-22.6471494502305 + 30 +0.0 + 10 +23.63015229866601 + 20 +-22.62918471375484 + 30 +0.0 + 10 +23.61218756219046 + 20 +-22.61121997727921 + 30 +0.0 + 10 +23.45769126709231 + 20 +-22.47468841865669 + 30 +0.0 + 10 +23.23133602609166 + 20 +-22.42797944593182 + 30 +0.0 + 10 +23.03731599497059 + 20 +-22.4890599885411 + 30 +0.0 + 10 +23.01216492531249 + 20 +-22.49624632172357 + 30 +0.0 + 10 +22.98342178554374 + 20 +-22.50702472501671 + 30 +0.0 + 10 +22.95827071588565 + 20 +-22.51780312830988 + 30 +0.0 + 10 +22.95827071588565 + 20 +-22.51780312830988 + 30 +0.0 + 10 +22.93311964622762 + 20 +-22.53217469819431 + 30 +0.0 + 10 +22.93311964622762 + 20 +-22.53217469819431 + 30 +0.0 + 10 +22.93311964622762 + 20 +-22.53217469819431 + 30 +0.0 + 10 +22.91874697986271 + 20 +-22.53936103137673 + 30 +0.0 + 10 +22.91874697986271 + 20 +-22.53936103137673 + 30 +0.0 + 10 +22.91874697986271 + 20 +-22.53936103137673 + 30 +0.0 + 10 +22.90437431349778 + 20 +-22.5465473645592 + 30 +0.0 + 10 +22.90437431349778 + 20 +-22.5465473645592 + 30 +0.0 + 10 +22.90437431349778 + 20 +-22.5465473645592 + 30 +0.0 + 10 +22.89718798031532 + 20 +-22.55014053115042 + 30 +0.0 + 10 +22.89718798031532 + 20 +-22.55014053115042 + 30 +0.0 + 10 +22.89718798031532 + 20 +-22.55014053115042 + 30 +0.0 + 10 +22.79658589464412 + 20 +-22.61122107375973 + 30 +0.0 + 10 +22.79658589464412 + 20 +-22.61122107375973 + 30 +0.0 + 10 +22.79658589464412 + 20 +-22.61122107375973 + 30 +0.0 + 10 +22.59178746022983 + 20 +-22.72978789590661 + 30 +0.0 + 10 +22.59178746022983 + 20 +-22.72978789590661 + 30 +0.0 + 10 +22.59178746022983 + 20 +-22.72978789590661 + 30 +0.0 + 10 +22.18578485447304 + 20 +-22.96692263668092 + 30 +0.0 + 10 +22.18578485447304 + 20 +-22.96692263668092 + 30 +0.0 + 10 +22.18578485447304 + 20 +-22.96692263668092 + 30 +0.0 + 10 +21.90553540404557 + 20 +-23.12860526496148 + 30 +0.0 + 10 +21.90553540404557 + 20 +-23.12860526496148 + 30 +0.0 + 10 +21.90553540404557 + 20 +-22.96332947008967 + 30 +0.0 + 10 +21.90553540404557 + 20 +-22.79446160510714 + 30 +0.0 + 10 +21.90912747415627 + 20 +-22.62559264364412 + 30 +0.0 + 10 +21.90912747415627 + 20 +-22.52499055797287 + 30 +0.0 + 10 +21.90912747415627 + 20 +-22.42798054241232 + 30 +0.0 + 10 +21.90912747415627 + 20 +-22.32737845674102 + 30 +0.0 + 10 +21.90912747415627 + 20 +-22.27707741390539 + 30 +0.0 + 10 +21.90912747415627 + 20 +-22.22677637106979 + 30 +0.0 + 10 +21.90912747415627 + 20 +-22.17647423175362 + 30 +0.0 + 10 +21.90912747415627 + 20 +-22.15132316209555 + 30 +0.0 + 10 +21.90553540404557 + 20 +-22.11898685573556 + 30 +0.0 + 10 +21.90553540404557 + 20 +-22.09024371596673 + 30 +0.0 + 10 +21.90553540404557 + 20 +-22.06150057619794 + 30 +0.0 + 10 +21.89834907086311 + 20 +-22.03275633994867 + 30 +0.0 + 10 +21.89475700075238 + 20 +-22.00401320017987 + 30 +0.0 + 10 +21.85523545769037 + 20 +-21.77047162599683 + 30 +0.0 + 10 +21.7258880392893 + 20 +-21.55130162169815 + 30 +0.0 + 10 +21.53186910464877 + 20 +-21.40399111154223 + 30 +0.0 + 10 +21.43485908908817 + 20 +-21.32853899904851 + 30 +0.0 + 10 +21.32707067023451 + 20 +-21.27464478962166 + 30 +0.0 + 10 +21.20850384808755 + 20 +-21.23871531667042 + 30 +0.0 + 10 +21.17976070831881 + 20 +-21.23152953172818 + 30 +0.0 + 10 +21.15101647206951 + 20 +-21.22434374678601 + 30 +0.0 + 10 +21.11868016570952 + 20 +-21.21715741360354 + 30 +0.0 + 10 +21.0863438593495 + 20 +-21.21356424701237 + 30 +0.0 + 10 +21.05041438639821 + 20 +-21.20637846207018 + 30 +0.0 + 10 +21.02167015014892 + 20 +-21.20278584371918 + 30 +0.0 + 10 +21.02167015014892 + 20 +-21.20278584371918 + 30 +0.0 + 10 +20.94262487106397 + 20 +-21.19919267712794 + 30 +0.0 + 10 +20.94262487106397 + 20 +-21.19919267712794 + 30 +0.0 + 10 +20.94262487106397 + 20 +-21.19919267712794 + 30 +0.0 + 10 +20.85639435527714 + 20 +-21.19919267712794 + 30 +0.0 + 10 +20.85639435527714 + 20 +-21.19919267712794 + 30 +0.0 + 10 +20.64441068416095 + 20 +-21.19919267712794 + 30 +0.0 + 10 +20.43242701304472 + 20 +-21.20278584371918 + 30 +0.0 + 10 +20.21685017533726 + 20 +-21.20278584371918 + 30 +0.0 + 10 +19.35454282450751 + 20 +-21.20637901031038 + 30 +0.0 + 10 +18.46349233390896 + 20 +-21.22075058019482 + 30 +0.0 + 10 +17.55806917694559 + 20 +-21.22793636513701 + 30 +0.0 + 10 +16.65264601998211 + 20 +-21.23871531667042 + 30 +0.0 + 10 +15.73285238961474 + 20 +-21.24230793502137 + 30 +0.0 + 10 +14.8130576627669 + 20 +-21.24230793502137 + 30 +0.0 + 10 +14.55436501892579 + 20 +-21.23871476843017 + 30 +0.0 + 10 +14.29926663815642 + 20 +-21.35368952046633 + 30 +0.0 + 10 +14.12680451010217 + 20 +-21.54052212192449 + 30 +0.0 + 10 +14.04057399431526 + 20 +-21.6339389708938 + 30 +0.0 + 10 +13.972308215004 + 20 +-21.74532055633876 + 30 +0.0 + 10 +13.92919240887035 + 20 +-21.86748054507686 + 30 +0.0 + 10 +13.90763450580347 + 20 +-21.92856108768617 + 30 +0.0 + 10 +13.89326293591906 + 20 +-21.98964053381496 + 30 +0.0 + 10 +13.88607660273662 + 20 +-22.05790631312624 + 30 +0.0 + 10 +13.88607660273662 + 20 +-22.06509264630871 + 30 +0.0 + 10 +13.88248343614542 + 20 +-22.07587104960185 + 30 +0.0 + 10 +13.88248343614542 + 20 +-22.08305738278432 + 30 +0.0 + 10 +13.88248343614542 + 20 +-22.08305738278432 + 30 +0.0 + 10 +13.88248343614542 + 20 +-22.10461528585114 + 30 +0.0 + 10 +13.88248343614542 + 20 +-22.10461528585114 + 30 +0.0 + 10 +13.88248343614542 + 20 +-22.10461528585114 + 30 +0.0 + 10 +13.87889026955418 + 20 +-22.14773109198485 + 30 +0.0 + 10 +13.87889026955418 + 20 +-22.14773109198485 + 30 +0.0 + 10 +13.87889026955418 + 20 +-22.14773109198485 + 30 +0.0 + 10 +13.87889026955418 + 20 +-22.15850949527801 + 30 +0.0 + 10 +13.87889026955418 + 20 +-22.15850949527801 + 30 +0.0 + 10 +13.87889026955418 + 20 +-22.15850949527801 + 30 +0.0 + 10 +13.87889026955418 + 20 +-22.1728810651624 + 30 +0.0 + 10 +13.87889026955418 + 20 +-22.1728810651624 + 30 +0.0 + 10 +13.87889026955418 + 20 +-22.1728810651624 + 30 +0.0 + 10 +13.87889026955418 + 20 +-22.19443896822923 + 30 +0.0 + 10 +13.87889026955418 + 20 +-22.19443896822923 + 30 +0.0 + 10 +13.87889026955418 + 20 +-22.19443896822923 + 30 +0.0 + 10 +13.87889026955418 + 20 +-22.23755477436293 + 30 +0.0 + 10 +13.87889026955418 + 20 +-22.23755477436293 + 30 +0.0 + 10 +13.87889026955418 + 20 +-22.69745158954662 + 30 +0.0 + 10 +13.87529710296296 + 20 +-23.15375633461953 + 30 +0.0 + 10 +13.86811186626101 + 20 +-23.60646791310127 + 30 +0.0 + 10 +13.85733346296787 + 20 +-24.51189107006472 + 30 +0.0 + 10 +13.8501471297854 + 20 +-25.40294156066327 + 30 +0.0 + 10 +13.83936872649221 + 20 +-26.26524891149299 + 30 +0.0 + 10 +13.83577555990099 + 20 +-26.6964025869079 + 30 +0.0 + 10 +13.83218239330977 + 20 +-27.12036992914035 + 30 +0.0 + 10 +13.82859032319905 + 20 +-27.5371520346708 + 30 +0.0 + 10 +13.82859032319905 + 20 +-27.74554253919579 + 30 +0.0 + 10 +13.82499715660785 + 20 +-27.95034097361005 + 30 +0.0 + 10 +13.82499715660785 + 20 +-28.15513940802432 + 30 +0.0 + 10 +13.82499715660785 + 20 +-28.27729939676247 + 30 +0.0 + 10 +13.85014822626587 + 20 +-28.39586731538988 + 30 +0.0 + 10 +13.89326293591906 + 20 +-28.50724780435429 + 30 +0.0 + 10 +13.93637874205276 + 20 +-28.61862938979922 + 30 +0.0 + 10 +14.0010513547728 + 20 +-28.7192314754705 + 30 +0.0 + 10 +14.08009663385774 + 20 +-28.80546199125739 + 30 +0.0 + 10 +14.15914191294264 + 20 +-28.89169250704425 + 30 +0.0 + 10 +14.25255766543149 + 20 +-28.96355145294673 + 30 +0.0 + 10 +14.35316084758326 + 20 +-29.01744566237362 + 30 +0.0 + 10 +14.35316084758326 + 20 +-29.01744566237362 + 30 +0.0 + 10 +14.43220612666821 + 20 +-29.05337513532486 + 30 +0.0 + 10 +14.43220612666821 + 20 +-29.05337513532486 + 30 +0.0 + 10 +14.45735719632623 + 20 +-29.064153538618 + 30 +0.0 + 10 +14.48610033609505 + 20 +-29.07133987180047 + 30 +0.0 + 10 +14.51484347586383 + 20 +-29.08211827509366 + 30 +0.0 + 10 +14.56873768529072 + 20 +-29.09648984497802 + 30 +0.0 + 10 +14.62263189471757 + 20 +-29.11086141486243 + 30 +0.0 + 10 +14.68730450743758 + 20 +-29.11804774804487 + 30 +0.0 + 10 +14.70167607732199 + 20 +-29.12164091463612 + 30 +0.0 + 10 +14.71964081379759 + 20 +-29.12164091463612 + 30 +0.0 + 10 +14.73042031357128 + 20 +-29.12523408122732 + 30 +0.0 + 10 +14.73042031357128 + 20 +-29.12523408122732 + 30 +0.0 + 10 +14.76634978652252 + 20 +-29.12523408122732 + 30 +0.0 + 10 +14.76634978652252 + 20 +-29.12523408122732 + 30 +0.0 + 10 +14.76634978652252 + 20 +-29.12523408122732 + 30 +0.0 + 10 +14.80227925947373 + 20 +-29.12523408122732 + 30 +0.0 + 10 +14.80227925947373 + 20 +-29.12523408122732 + 30 +0.0 + 10 +14.80227925947373 + 20 +-29.12523408122732 + 30 +0.0 + 10 +14.80946559265617 + 20 +-29.12523408122732 + 30 +0.0 + 10 +14.80946559265617 + 20 +-29.12523408122732 + 30 +0.0 + 10 +14.82743032913183 + 20 +-29.12523408122732 + 30 +0.0 + 10 +14.82024399594937 + 20 +-29.12523408122732 + 30 +0.0 + 10 +14.82743032913183 + 20 +-29.12523408122732 + 30 +0.0 + 10 +14.82743032913183 + 20 +-29.12523408122732 + 30 +0.0 + 10 +14.84539506560746 + 20 +-29.12523408122732 + 30 +0.0 + 10 +14.84539506560746 + 20 +-29.12523408122732 + 30 +0.0 + 10 +14.89210294185184 + 20 +-29.12523408122732 + 30 +0.0 + 10 +14.93881191457679 + 20 +-29.12523408122732 + 30 +0.0 + 10 +14.98911295741239 + 20 +-29.12523408122732 + 30 +0.0 + 10 +15.08252980638175 + 20 +-29.12523408122732 + 30 +0.0 + 10 +15.17594665535105 + 20 +-29.12164091463612 + 30 +0.0 + 10 +15.26936240783986 + 20 +-29.12164091463612 + 30 +0.0 + 10 +15.64302870723671 + 20 +-29.11804774804487 + 30 +0.0 + 10 +16.00232343674907 + 20 +-29.11445458145365 + 30 +0.0 + 10 +16.34724659637709 + 20 +-29.11086251134293 + 30 +0.0 + 10 +17.03709291563311 + 20 +-29.10367617816049 + 30 +0.0 + 10 +17.6694507623905 + 20 +-29.09649094145852 + 30 +0.0 + 10 +18.2299507597259 + 20 +-29.08930460827605 + 30 +0.0 + 10 +18.79045075706136 + 20 +-29.08211827509366 + 30 +0.0 + 10 +19.27909180849429 + 20 +-29.07852620498291 + 30 +0.0 + 10 +19.6815012476599 + 20 +-29.07852620498291 + 30 +0.0 + 10 +20.48632122247161 + 20 +-29.07493303839169 + 30 +0.0 + 10 +20.94621913413572 + 20 +-29.07493303839169 + 30 +0.0 + 10 +20.94621913413572 + 20 +-29.07493303839169 + 30 +0.0 + 10 +20.94621913413572 + 20 +-29.07493303839169 + 30 +0.0 + 10 +20.97855544049574 + 20 +-29.07493303839169 + 30 +0.0 + 10 +21.03963598310502 + 20 +-29.07133987180047 + 30 +0.0 + 10 +21.10071652571433 + 20 +-29.064153538618 + 30 +0.0 + 10 +21.19413227820319 + 20 +-29.04978196873364 + 30 +0.0 + 10 +21.30551386364813 + 20 +-29.00307409248921 + 30 +0.0 + 10 +21.41689544909309 + 20 +-28.95636621624478 + 30 +0.0 + 10 +21.54624177101361 + 20 +-28.87372777056867 + 30 +0.0 + 10 +21.66121542656927 + 20 +-28.7443814486481 + 30 +0.0 + 10 +21.71510963599621 + 20 +-28.6761156693368 + 30 +0.0 + 10 +21.76900384542306 + 20 +-28.5970703902519 + 30 +0.0 + 10 +21.80852648496552 + 20 +-28.50724670787382 + 30 +0.0 + 10 +21.81571281814798 + 20 +-28.48209563821572 + 30 +0.0 + 10 +21.8264901249606 + 20 +-28.46053883162939 + 30 +0.0 + 10 +21.83367645814307 + 20 +-28.43538776197134 + 30 +0.0 + 10 +21.84086279132553 + 20 +-28.41023669231324 + 30 +0.0 + 10 +21.84804912450798 + 20 +-28.38508671913567 + 30 +0.0 + 10 +21.85523326472942 + 20 +-28.35993564947762 + 30 +0.0 + 10 +21.86241959791189 + 20 +-28.33478457981957 + 30 +0.0 + 10 +21.86601166802262 + 20 +-28.30604144005075 + 30 +0.0 + 10 +21.86960593109428 + 20 +-28.27729830028195 + 30 +0.0 + 10 +21.873198001205 + 20 +-28.26292673039754 + 30 +0.0 + 10 +21.873198001205 + 20 +-28.24855516051318 + 30 +0.0 + 10 +21.87679226427672 + 20 +-28.23418249414825 + 30 +0.0 + 10 +21.87679226427672 + 20 +-28.22340409085511 + 30 +0.0 + 10 +21.87679226427672 + 20 +-28.20903142449025 + 30 +0.0 + 10 +21.88038433438745 + 20 +-28.19825302119706 + 30 +0.0 + 10 +21.88038433438745 + 20 +-28.18747461790387 + 30 +0.0 + 10 +21.88038433438745 + 20 +-28.17310195153896 + 30 +0.0 + 10 +21.88397640449817 + 20 +-28.15873038165457 + 30 +0.0 + 10 +21.88397640449817 + 20 +-28.15873038165457 + 30 +0.0 + 10 +21.88397640449817 + 20 +-28.14435881177016 + 30 +0.0 + 10 +21.88397640449817 + 20 +-28.14435881177016 + 30 +0.0 + 10 +21.88397640449817 + 20 +-28.14435881177016 + 30 +0.0 + 10 +21.88397640449817 + 20 +-28.13358040847702 + 30 +0.0 + 10 +21.88397640449817 + 20 +-28.13358040847702 + 30 +0.0 + 10 +21.88397640449817 + 20 +-28.13358040847702 + 30 +0.0 + 10 +21.88397640449817 + 20 +-28.11561567200139 + 30 +0.0 + 10 +21.88397640449817 + 20 +-28.11561567200139 + 30 +0.0 + 10 +21.88397640449817 + 20 +-28.09046460234329 + 30 +0.0 + 10 +21.88397640449817 + 20 +-28.06531462916574 + 30 +0.0 + 10 +21.88397640449817 + 20 +-28.03657039291645 + 30 +0.0 + 10 +21.88397640449817 + 20 +-27.9826761834896 + 30 +0.0 + 10 +21.88397640449817 + 20 +-27.92878197406276 + 30 +0.0 + 10 +21.88397640449817 + 20 +-27.87488776463589 + 30 +0.0 + 10 +21.88397640449817 + 20 +-27.76350617919095 + 30 +0.0 + 10 +21.88397640449817 + 20 +-27.64853252363529 + 30 +0.0 + 10 +21.88397640449817 + 20 +-27.52637143841664 + 30 +0.0 + 10 +21.88397640449817 + 20 +-27.28205146094041 + 30 +0.0 + 10 +21.88756847460889 + 20 +-27.01976565050806 + 30 +0.0 + 10 +21.89116273768064 + 20 +-26.73951620008059 + 30 +0.0 + 10 +21.89475480779136 + 20 +-26.17901620274514 + 30 +0.0 + 10 +21.90194114097383 + 20 +-25.54665725950723 + 30 +0.0 + 10 +21.90553540404557 + 20 +-24.85681203673173 + 30 +0.0 + 10 +21.90553540404557 + 20 +-24.75261678446923 + 30 +0.0 + 10 +21.90553540404557 + 20 +-24.64482836561549 + 30 +0.0 + 10 +21.90912747415627 + 20 +-24.53703994676183 + 30 +0.0 + 10 +21.90912747415627 + 20 +-24.53703994676183 + 30 +0.0 + 10 +22.25045746719305 + 20 +-24.33942784552998 + 30 +0.0 + 10 +22.25045746719305 + 20 +-24.33942784552998 + 30 +0.0 + 10 +22.25045746719305 + 20 +-24.33942784552998 + 30 +0.0 + 10 +23.06605694177839 + 20 +-23.86515836398141 + 30 +0.0 + 10 +23.06605694177839 + 20 +-23.86515836398141 + 30 +0.0 + 10 +23.06605694177839 + 20 +-23.86515836398141 + 30 +0.0 + 10 +23.47205954753518 + 20 +-23.62802362320713 + 30 +0.0 + 10 +23.47205954753518 + 20 +-23.62802362320713 + 30 +0.0 + 10 +23.47205954753518 + 20 +-23.62802362320713 + 30 +0.0 + 10 +23.52236168685133 + 20 +-23.59928048343833 + 30 +0.0 + 10 +23.52236168685133 + 20 +-23.59928048343833 + 30 +0.0 + 10 +23.54032642332696 + 20 +-23.58850208014517 + 30 +0.0 + 10 +23.56547749298498 + 20 +-23.57412941378023 + 30 +0.0 + 10 +23.58703429957134 + 20 +-23.55616467730463 + 30 +0.0 + 10 +23.62655584263335 + 20 +-23.52382837094463 + 30 +0.0 + 10 +23.66607957865628 + 20 +-23.48789889799339 + 30 +0.0 + 10 +23.69841478853583 + 20 +-23.44837625845091 + 30 +0.0 + 10 +23.76308740125584 + 20 +-23.36933097936597 + 30 +0.0 + 10 +23.80620320738952 + 20 +-23.26872889369474 + 30 +0.0 + 10 +23.82416794386515 + 20 +-23.16812680802344 + 30 +0.0 + 10 +23.82776220693689 + 20 +-23.06752472235217 + 30 +0.0 + 10 +23.82057587375443 + 20 +-22.96332947008967 + 30 +0.0 + 10 +23.78464640080316 + 20 +-22.86631945452912 + 30 +0.0 + 0 +SPLINE + 5 +42 +330 +1F +100 +AcDbEntity + 8 +Layer 1 +370 + 0 +100 +AcDbSpline +210 +0.0 +220 +0.0 +230 +1.0 + 70 + 11 + 71 + 3 + 72 + 284 + 73 + 280 + 74 + 0 + 42 +0.0000000001 + 43 +0.0000000001 + 12 +1.0 + 22 +0.0 + 32 +0.0 + 13 +1.0 + 23 +0.0 + 33 +0.0 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +1.0 + 40 +1.0 + 40 +1.0 + 40 +2.0 + 40 +2.0 + 40 +2.0 + 40 +3.0 + 40 +3.0 + 40 +3.0 + 40 +4.0 + 40 +4.0 + 40 +4.0 + 40 +5.0 + 40 +5.0 + 40 +5.0 + 40 +6.0 + 40 +6.0 + 40 +6.0 + 40 +7.0 + 40 +7.0 + 40 +7.0 + 40 +8.0 + 40 +8.0 + 40 +8.0 + 40 +9.0 + 40 +9.0 + 40 +9.0 + 40 +10.0 + 40 +10.0 + 40 +10.0 + 40 +11.0 + 40 +11.0 + 40 +11.0 + 40 +12.0 + 40 +12.0 + 40 +12.0 + 40 +13.0 + 40 +13.0 + 40 +13.0 + 40 +14.0 + 40 +14.0 + 40 +14.0 + 40 +15.0 + 40 +15.0 + 40 +15.0 + 40 +16.0 + 40 +16.0 + 40 +16.0 + 40 +17.0 + 40 +17.0 + 40 +17.0 + 40 +18.0 + 40 +18.0 + 40 +18.0 + 40 +19.0 + 40 +19.0 + 40 +19.0 + 40 +20.0 + 40 +20.0 + 40 +20.0 + 40 +21.0 + 40 +21.0 + 40 +21.0 + 40 +22.0 + 40 +22.0 + 40 +22.0 + 40 +23.0 + 40 +23.0 + 40 +23.0 + 40 +24.0 + 40 +24.0 + 40 +24.0 + 40 +25.0 + 40 +25.0 + 40 +25.0 + 40 +26.0 + 40 +26.0 + 40 +26.0 + 40 +27.0 + 40 +27.0 + 40 +27.0 + 40 +28.0 + 40 +28.0 + 40 +28.0 + 40 +29.0 + 40 +29.0 + 40 +29.0 + 40 +30.0 + 40 +30.0 + 40 +30.0 + 40 +31.0 + 40 +31.0 + 40 +31.0 + 40 +32.0 + 40 +32.0 + 40 +32.0 + 40 +33.0 + 40 +33.0 + 40 +33.0 + 40 +34.0 + 40 +34.0 + 40 +34.0 + 40 +35.0 + 40 +35.0 + 40 +35.0 + 40 +36.0 + 40 +36.0 + 40 +36.0 + 40 +37.0 + 40 +37.0 + 40 +37.0 + 40 +38.0 + 40 +38.0 + 40 +38.0 + 40 +39.0 + 40 +39.0 + 40 +39.0 + 40 +40.0 + 40 +40.0 + 40 +40.0 + 40 +41.0 + 40 +41.0 + 40 +41.0 + 40 +42.0 + 40 +42.0 + 40 +42.0 + 40 +43.0 + 40 +43.0 + 40 +43.0 + 40 +44.0 + 40 +44.0 + 40 +44.0 + 40 +45.0 + 40 +45.0 + 40 +45.0 + 40 +46.0 + 40 +46.0 + 40 +46.0 + 40 +47.0 + 40 +47.0 + 40 +47.0 + 40 +48.0 + 40 +48.0 + 40 +48.0 + 40 +49.0 + 40 +49.0 + 40 +49.0 + 40 +50.0 + 40 +50.0 + 40 +50.0 + 40 +51.0 + 40 +51.0 + 40 +51.0 + 40 +52.0 + 40 +52.0 + 40 +52.0 + 40 +53.0 + 40 +53.0 + 40 +53.0 + 40 +54.0 + 40 +54.0 + 40 +54.0 + 40 +55.0 + 40 +55.0 + 40 +55.0 + 40 +56.0 + 40 +56.0 + 40 +56.0 + 40 +57.0 + 40 +57.0 + 40 +57.0 + 40 +58.0 + 40 +58.0 + 40 +58.0 + 40 +59.0 + 40 +59.0 + 40 +59.0 + 40 +60.0 + 40 +60.0 + 40 +60.0 + 40 +61.0 + 40 +61.0 + 40 +61.0 + 40 +62.0 + 40 +62.0 + 40 +62.0 + 40 +63.0 + 40 +63.0 + 40 +63.0 + 40 +64.0 + 40 +64.0 + 40 +64.0 + 40 +65.0 + 40 +65.0 + 40 +65.0 + 40 +66.0 + 40 +66.0 + 40 +66.0 + 40 +67.0 + 40 +67.0 + 40 +67.0 + 40 +68.0 + 40 +68.0 + 40 +68.0 + 40 +69.0 + 40 +69.0 + 40 +69.0 + 40 +70.0 + 40 +70.0 + 40 +70.0 + 40 +71.0 + 40 +71.0 + 40 +71.0 + 40 +72.0 + 40 +72.0 + 40 +72.0 + 40 +73.0 + 40 +73.0 + 40 +73.0 + 40 +74.0 + 40 +74.0 + 40 +74.0 + 40 +75.0 + 40 +75.0 + 40 +75.0 + 40 +76.0 + 40 +76.0 + 40 +76.0 + 40 +77.0 + 40 +77.0 + 40 +77.0 + 40 +78.0 + 40 +78.0 + 40 +78.0 + 40 +79.0 + 40 +79.0 + 40 +79.0 + 40 +80.0 + 40 +80.0 + 40 +80.0 + 40 +81.0 + 40 +81.0 + 40 +81.0 + 40 +82.0 + 40 +82.0 + 40 +82.0 + 40 +83.0 + 40 +83.0 + 40 +83.0 + 40 +84.0 + 40 +84.0 + 40 +84.0 + 40 +85.0 + 40 +85.0 + 40 +85.0 + 40 +86.0 + 40 +86.0 + 40 +86.0 + 40 +87.0 + 40 +87.0 + 40 +87.0 + 40 +88.0 + 40 +88.0 + 40 +88.0 + 40 +89.0 + 40 +89.0 + 40 +89.0 + 40 +90.0 + 40 +90.0 + 40 +90.0 + 40 +91.0 + 40 +91.0 + 40 +91.0 + 40 +92.0 + 40 +92.0 + 40 +92.0 + 40 +93.0 + 40 +93.0 + 40 +93.0 + 40 +93.0 + 10 +21.65402909338682 + 20 +-24.86040520332295 + 30 +0.0 + 10 +21.66121542656927 + 20 +-25.55025152257897 + 30 +0.0 + 10 +21.66480749668002 + 20 +-26.1826093693364 + 30 +0.0 + 10 +21.66840066327126 + 20 +-26.74310936667181 + 30 +0.0 + 10 +21.67199382986246 + 20 +-27.0233588170993 + 30 +0.0 + 10 +21.67199382986246 + 20 +-27.28564462753164 + 30 +0.0 + 10 +21.67558699645373 + 20 +-27.52996460500786 + 30 +0.0 + 10 +21.67558699645373 + 20 +-27.65212459374601 + 30 +0.0 + 10 +21.67558699645373 + 20 +-27.76709934578215 + 30 +0.0 + 10 +21.67558699645373 + 20 +-27.87848093122711 + 30 +0.0 + 10 +21.67558699645373 + 20 +-27.93237514065395 + 30 +0.0 + 10 +21.67558699645373 + 20 +-27.98986251667207 + 30 +0.0 + 10 +21.67558699645373 + 20 +-28.04016355950767 + 30 +0.0 + 10 +21.67558699645373 + 20 +-28.06531462916574 + 30 +0.0 + 10 +21.67558699645373 + 20 +-28.09405776893456 + 30 +0.0 + 10 +21.67558699645373 + 20 +-28.11920883859261 + 30 +0.0 + 10 +21.67558699645373 + 20 +-28.11920883859261 + 30 +0.0 + 10 +21.67558699645373 + 20 +-28.13717357506822 + 30 +0.0 + 10 +21.67558699645373 + 20 +-28.13717357506822 + 30 +0.0 + 10 +21.67558699645373 + 20 +-28.13717357506822 + 30 +0.0 + 10 +21.67558699645373 + 20 +-28.14795197836141 + 30 +0.0 + 10 +21.67558699645373 + 20 +-28.14795197836141 + 30 +0.0 + 10 +21.67558699645373 + 20 +-28.14795197836141 + 30 +0.0 + 10 +21.67558699645373 + 20 +-28.14795197836141 + 30 +0.0 + 10 +21.67558699645373 + 20 +-28.14795197836141 + 30 +0.0 + 10 +21.67558699645373 + 20 +-28.14795197836141 + 30 +0.0 + 10 +21.67558699645373 + 20 +-28.15154514495263 + 30 +0.0 + 10 +21.67558699645373 + 20 +-28.15154514495263 + 30 +0.0 + 10 +21.67558699645373 + 20 +-28.16591671483704 + 30 +0.0 + 10 +21.67558699645373 + 20 +-28.17669621461068 + 30 +0.0 + 10 +21.67558699645373 + 20 +-28.19106778449506 + 30 +0.0 + 10 +21.67558699645373 + 20 +-28.20184618778825 + 30 +0.0 + 10 +21.67558699645373 + 20 +-28.21621885415316 + 30 +0.0 + 10 +21.67558699645373 + 20 +-28.22699725744633 + 30 +0.0 + 10 +21.67558699645373 + 20 +-28.23777566073952 + 30 +0.0 + 10 +21.67199382986246 + 20 +-28.24855516051318 + 30 +0.0 + 10 +21.67199382986246 + 20 +-28.25574039721512 + 30 +0.0 + 10 +21.66840066327126 + 20 +-28.27729830028195 + 30 +0.0 + 10 +21.66840066327126 + 20 +-28.29526303675756 + 30 +0.0 + 10 +21.66121542656927 + 20 +-28.31682093982444 + 30 +0.0 + 10 +21.65402909338682 + 20 +-28.33837884289131 + 30 +0.0 + 10 +21.6504370232761 + 20 +-28.35634357936689 + 30 +0.0 + 10 +21.64325069009374 + 20 +-28.37790148243372 + 30 +0.0 + 10 +21.63606435691127 + 20 +-28.39586621890933 + 30 +0.0 + 10 +21.62887912020927 + 20 +-28.41742412197621 + 30 +0.0 + 10 +21.62169278702681 + 20 +-28.43538885845181 + 30 +0.0 + 10 +21.58935648066679 + 20 +-28.50724780435429 + 30 +0.0 + 10 +21.54983384112433 + 20 +-28.5719204170743 + 30 +0.0 + 10 +21.50671803499068 + 20 +-28.6258146265012 + 30 +0.0 + 10 +21.41689435261254 + 20 +-28.73000987876369 + 30 +0.0 + 10 +21.31629226694132 + 20 +-28.79468358796422 + 30 +0.0 + 10 +21.22646858456318 + 20 +-28.83061306091546 + 30 +0.0 + 10 +21.14023806877634 + 20 +-28.8665425338667 + 30 +0.0 + 10 +21.06478595628267 + 20 +-28.87732093715989 + 30 +0.0 + 10 +21.01807808003819 + 20 +-28.88450727034231 + 30 +0.0 + 10 +20.96777703720254 + 20 +-28.88810043693353 + 30 +0.0 + 10 +20.94262596754452 + 20 +-28.88810043693353 + 30 +0.0 + 10 +20.94262596754452 + 20 +-28.88810043693353 + 30 +0.0 + 10 +20.94262596754452 + 20 +-28.88810043693353 + 30 +0.0 + 10 +20.48272915236084 + 20 +-28.88450727034231 + 30 +0.0 + 10 +19.67790808106868 + 20 +-28.88450727034231 + 30 +0.0 + 10 +19.27549754542257 + 20 +-28.88450727034231 + 30 +0.0 + 10 +18.78685759047014 + 20 +-28.88091410375106 + 30 +0.0 + 10 +18.22635759313468 + 20 +-28.87372886704917 + 30 +0.0 + 10 +17.66585759579925 + 20 +-28.8665425338667 + 30 +0.0 + 10 +17.03349865256137 + 20 +-28.85935729716476 + 30 +0.0 + 10 +16.34365342978584 + 20 +-28.85217096398229 + 30 +0.0 + 10 +15.99873027015784 + 20 +-28.84857779739107 + 30 +0.0 + 10 +15.63943554064543 + 20 +-28.84498463079982 + 30 +0.0 + 10 +15.26576924124864 + 20 +-28.8413925606891 + 30 +0.0 + 10 +15.17235239227933 + 20 +-28.8413925606891 + 30 +0.0 + 10 +15.07893554331 + 20 +-28.83779939409788 + 30 +0.0 + 10 +14.98551979082117 + 20 +-28.83779939409788 + 30 +0.0 + 10 +14.93881191457679 + 20 +-28.83779939409788 + 30 +0.0 + 10 +14.89210294185184 + 20 +-28.83779939409788 + 30 +0.0 + 10 +14.84180189901619 + 20 +-28.83779939409788 + 30 +0.0 + 10 +14.84180189901619 + 20 +-28.83779939409788 + 30 +0.0 + 10 +14.82383716254058 + 20 +-28.83779939409788 + 30 +0.0 + 10 +14.82383716254058 + 20 +-28.83779939409788 + 30 +0.0 + 10 +14.82383716254058 + 20 +-28.83779939409788 + 30 +0.0 + 10 +14.82383716254058 + 20 +-28.83779939409788 + 30 +0.0 + 10 +14.82024399594937 + 20 +-28.83779939409788 + 30 +0.0 + 10 +14.82024399594937 + 20 +-28.83779939409788 + 30 +0.0 + 10 +14.8130576627669 + 20 +-28.83779939409788 + 30 +0.0 + 10 +14.8130576627669 + 20 +-28.83779939409788 + 30 +0.0 + 10 +14.8130576627669 + 20 +-28.83779939409788 + 30 +0.0 + 10 +14.77712818981571 + 20 +-28.83420622750668 + 30 +0.0 + 10 +14.77712818981571 + 20 +-28.83420622750668 + 30 +0.0 + 10 +14.77712818981571 + 20 +-28.83420622750668 + 30 +0.0 + 10 +14.74119871686442 + 20 +-28.83061306091546 + 30 +0.0 + 10 +14.74119871686442 + 20 +-28.83061306091546 + 30 +0.0 + 10 +14.73042031357128 + 20 +-28.83061306091546 + 30 +0.0 + 10 +14.72323398038881 + 20 +-28.82701989432422 + 30 +0.0 + 10 +14.7160476472064 + 20 +-28.82701989432422 + 30 +0.0 + 10 +14.68371134084638 + 20 +-28.82342672773299 + 30 +0.0 + 10 +14.64418870130392 + 20 +-28.81264832443986 + 30 +0.0 + 10 +14.60466606176144 + 20 +-28.80186882466617 + 30 +0.0 + 10 +14.60466606176144 + 20 +-28.80186882466617 + 30 +0.0 + 10 +14.55077185233459 + 20 +-28.78031092159929 + 30 +0.0 + 10 +14.55077185233459 + 20 +-28.78031092159929 + 30 +0.0 + 10 +14.55077185233459 + 20 +-28.78031092159929 + 30 +0.0 + 10 +14.49687764290772 + 20 +-28.75515985194129 + 30 +0.0 + 10 +14.49687764290772 + 20 +-28.75515985194129 + 30 +0.0 + 10 +14.42861186359647 + 20 +-28.71563721239881 + 30 +0.0 + 10 +14.36393815439593 + 20 +-28.66533616956316 + 30 +0.0 + 10 +14.31004394496904 + 20 +-28.60425562695385 + 30 +0.0 + 10 +14.20584869270654 + 20 +-28.48209563821572 + 30 +0.0 + 10 +14.14117498350606 + 20 +-28.32400617652638 + 30 +0.0 + 10 +14.13758291339534 + 20 +-28.15873038165457 + 30 +0.0 + 10 +14.13398974680407 + 20 +-27.9575251138315 + 30 +0.0 + 10 +14.13398974680407 + 20 +-27.75272777589779 + 30 +0.0 + 10 +14.13039658021287 + 20 +-27.54433617489227 + 30 +0.0 + 10 +14.12680341362165 + 20 +-27.1275540693618 + 30 +0.0 + 10 +14.12321024703043 + 20 +-26.70358672712935 + 30 +0.0 + 10 +14.1196181769197 + 20 +-26.27243305171449 + 30 +0.0 + 10 +14.10883977362651 + 20 +-25.41012570088472 + 30 +0.0 + 10 +14.10165344044405 + 20 +-24.51907521028617 + 30 +0.0 + 10 +14.09087503715088 + 20 +-23.61365205332271 + 30 +0.0 + 10 +14.08728187055969 + 20 +-23.16094047484097 + 30 +0.0 + 10 +14.08368870396847 + 20 +-22.70463682624856 + 30 +0.0 + 10 +14.08009663385774 + 20 +-22.24473891458441 + 30 +0.0 + 10 +14.08009663385774 + 20 +-22.24473891458441 + 30 +0.0 + 10 +14.08009663385774 + 20 +-22.20162310845068 + 30 +0.0 + 10 +14.08009663385774 + 20 +-22.20162310845068 + 30 +0.0 + 10 +14.08009663385774 + 20 +-22.20162310845068 + 30 +0.0 + 10 +14.08009663385774 + 20 +-22.18006520538382 + 30 +0.0 + 10 +14.08009663385774 + 20 +-22.18006520538382 + 30 +0.0 + 10 +14.08009663385774 + 20 +-22.18006520538382 + 30 +0.0 + 10 +14.08009663385774 + 20 +-22.18006520538382 + 30 +0.0 + 10 +14.08009663385774 + 20 +-22.18006520538382 + 30 +0.0 + 10 +14.08009663385774 + 20 +-22.18006520538382 + 30 +0.0 + 10 +14.08009663385774 + 20 +-22.18006520538382 + 30 +0.0 + 10 +14.08009663385774 + 20 +-22.17647203879265 + 30 +0.0 + 10 +14.08009663385774 + 20 +-22.17647203879265 + 30 +0.0 + 10 +14.08368980044896 + 20 +-22.12257782936576 + 30 +0.0 + 10 +14.08368980044896 + 20 +-22.12257782936576 + 30 +0.0 + 10 +14.08368980044896 + 20 +-22.12257782936576 + 30 +0.0 + 10 +14.08368980044896 + 20 +-22.10101992629893 + 30 +0.0 + 10 +14.08368980044896 + 20 +-22.10101992629893 + 30 +0.0 + 10 +14.08368980044896 + 20 +-22.09383359311646 + 30 +0.0 + 10 +14.08728296704016 + 20 +-22.09024152300577 + 30 +0.0 + 10 +14.08728296704016 + 20 +-22.0830551898233 + 30 +0.0 + 10 +14.09446930022263 + 20 +-22.03993938368959 + 30 +0.0 + 10 +14.10524770351579 + 20 +-21.98963834085397 + 30 +0.0 + 10 +14.11961927340018 + 20 +-21.93933729801837 + 30 +0.0 + 10 +14.15195557976022 + 20 +-21.84592044904901 + 30 +0.0 + 10 +14.20584978918706 + 20 +-21.75609676667093 + 30 +0.0 + 10 +14.27411556849832 + 20 +-21.68064465417721 + 30 +0.0 + 10 +14.41064712712083 + 20 +-21.52974042918984 + 30 +0.0 + 10 +14.60825922835263 + 20 +-21.43991729505198 + 30 +0.0 + 10 +14.8130576627669 + 20 +-21.43991729505198 + 30 +0.0 + 10 +15.73285238961474 + 20 +-21.43991729505198 + 30 +0.0 + 10 +16.6526471164626 + 20 +-21.44351046164318 + 30 +0.0 + 10 +17.55806917694559 + 20 +-21.45428886493634 + 30 +0.0 + 10 +18.46349233390896 + 20 +-21.46147464987853 + 30 +0.0 + 10 +19.35454282450751 + 20 +-21.472253601412 + 30 +0.0 + 10 +20.21685017533726 + 20 +-21.47943938635419 + 30 +0.0 + 10 +20.43242701304472 + 20 +-21.47943938635419 + 30 +0.0 + 10 +20.64800385075212 + 20 +-21.48303255294541 + 30 +0.0 + 10 +20.85639435527714 + 20 +-21.48303255294541 + 30 +0.0 + 10 +20.85639435527714 + 20 +-21.48303255294541 + 30 +0.0 + 10 +20.92825330117961 + 20 +-21.48303255294541 + 30 +0.0 + 10 +20.92825330117961 + 20 +-21.48303255294541 + 30 +0.0 + 10 +20.92825330117961 + 20 +-21.48303255294541 + 30 +0.0 + 10 +21.00729858026456 + 20 +-21.48662571953663 + 30 +0.0 + 10 +21.00729858026456 + 20 +-21.48662571953663 + 30 +0.0 + 10 +21.02885648333138 + 20 +-21.49021888612782 + 30 +0.0 + 10 +21.04682121980702 + 20 +-21.49381150447882 + 30 +0.0 + 10 +21.06837912287384 + 20 +-21.49740467107004 + 30 +0.0 + 10 +21.0899370259407 + 20 +-21.50099783766129 + 30 +0.0 + 10 +21.11149492900752 + 20 +-21.50818362260346 + 30 +0.0 + 10 +21.12945966548316 + 20 +-21.5117767891947 + 30 +0.0 + 10 +21.21209701467882 + 20 +-21.53692785885275 + 30 +0.0 + 10 +21.29114229376377 + 20 +-21.57644940191472 + 30 +0.0 + 10 +21.35940807307497 + 20 +-21.63034361134161 + 30 +0.0 + 10 +21.49593963169749 + 20 +-21.7345388636041 + 30 +0.0 + 10 +21.58935648066679 + 20 +-21.89262832529344 + 30 +0.0 + 10 +21.62169278702681 + 20 +-22.05790412016523 + 30 +0.0 + 10 +21.62528595361808 + 20 +-22.07946202323208 + 30 +0.0 + 10 +21.62887912020927 + 20 +-22.10101992629893 + 30 +0.0 + 10 +21.62887912020927 + 20 +-22.11898466277454 + 30 +0.0 + 10 +21.62887912020927 + 20 +-22.14054256584136 + 30 +0.0 + 10 +21.63247228680055 + 20 +-22.15850730231699 + 30 +0.0 + 10 +21.63247228680055 + 20 +-22.18365727549457 + 30 +0.0 + 10 +21.63247228680055 + 20 +-22.23395831833022 + 30 +0.0 + 10 +21.63247228680055 + 20 +-22.28425936116587 + 30 +0.0 + 10 +21.63247228680055 + 20 +-22.33456150048199 + 30 +0.0 + 10 +21.63247228680055 + 20 +-22.43516358615327 + 30 +0.0 + 10 +21.63247228680055 + 20 +-22.53576676830501 + 30 +0.0 + 10 +21.63247228680055 + 20 +-22.63277568738512 + 30 +0.0 + 10 +21.63247228680055 + 20 +-22.85553885827497 + 30 +0.0 + 10 +21.63247228680055 + 20 +-23.07830093268436 + 30 +0.0 + 10 +21.63606545339174 + 20 +-23.29387777039179 + 30 +0.0 + 10 +21.63606545339174 + 20 +-23.29387777039179 + 30 +0.0 + 10 +20.95340546731818 + 20 +-23.69269513944664 + 30 +0.0 + 10 +20.95340546731818 + 20 +-23.69269513944664 + 30 +0.0 + 10 +20.95340546731818 + 20 +-23.69269513944664 + 30 +0.0 + 10 +20.9390338974338 + 20 +-22.26988779128146 + 30 +0.0 + 10 +20.9390338974338 + 20 +-22.26988779128146 + 30 +0.0 + 10 +20.9390338974338 + 20 +-22.21599358185457 + 30 +0.0 + 10 +20.89591809130007 + 20 +-22.17287777572091 + 30 +0.0 + 10 +20.84202388187322 + 20 +-22.17287777572091 + 30 +0.0 + 10 +20.84202388187322 + 20 +-22.17287777572091 + 30 +0.0 + 10 +20.84202388187322 + 20 +-22.17287777572091 + 30 +0.0 + 10 +20.84202388187322 + 20 +-22.17287777572091 + 30 +0.0 + 10 +20.5941107378058 + 20 +-22.16928460912969 + 30 +0.0 + 10 +20.34619759373832 + 20 +-22.16569144253845 + 30 +0.0 + 10 +20.09828335319035 + 20 +-22.16209937242772 + 30 +0.0 + 10 +20.09828335319035 + 20 +-22.16209937242772 + 30 +0.0 + 10 +19.35454282450751 + 20 +-22.15132096913456 + 30 +0.0 + 10 +19.35454282450751 + 20 +-22.15132096913456 + 30 +0.0 + 10 +19.35454282450751 + 20 +-22.15132096913456 + 30 +0.0 + 10 +17.87065603021352 + 20 +-22.13694939925017 + 30 +0.0 + 10 +17.87065603021352 + 20 +-22.13694939925017 + 30 +0.0 + 10 +17.87065603021352 + 20 +-22.13694939925017 + 30 +0.0 + 10 +16.38676923591955 + 20 +-22.13335623265895 + 30 +0.0 + 10 +16.38676923591955 + 20 +-22.13335623265895 + 30 +0.0 + 10 +16.38676923591955 + 20 +-22.13335623265895 + 30 +0.0 + 10 +15.64302870723671 + 20 +-22.12976306606773 + 30 +0.0 + 10 +15.64302870723671 + 20 +-22.12976306606773 + 30 +0.0 + 10 +15.64302870723671 + 20 +-22.12976306606773 + 30 +0.0 + 10 +14.89928817855379 + 20 +-22.12976306606773 + 30 +0.0 + 10 +14.89928817855379 + 20 +-22.12976306606773 + 30 +0.0 + 10 +14.89928817855379 + 20 +-22.12976306606773 + 30 +0.0 + 10 +14.89928817855379 + 20 +-22.12976306606773 + 30 +0.0 + 10 +14.89928817855379 + 20 +-22.12976306606773 + 30 +0.0 + 10 +14.82383606606009 + 20 +-22.12976306606773 + 30 +0.0 + 10 +14.7627566199313 + 20 +-22.19084360867701 + 30 +0.0 + 10 +14.7627566199313 + 20 +-22.26988779128146 + 30 +0.0 + 10 +14.7627566199313 + 20 +-22.26988779128146 + 30 +0.0 + 10 +14.76634978652252 + 20 +-23.71784511262422 + 30 +0.0 + 10 +14.76634978652252 + 20 +-23.71784511262422 + 30 +0.0 + 10 +14.76634978652252 + 20 +-23.71784511262422 + 30 +0.0 + 10 +14.76634978652252 + 20 +-24.44002773824021 + 30 +0.0 + 10 +14.76634978652252 + 20 +-24.44002773824021 + 30 +0.0 + 10 +14.76634978652252 + 20 +-24.44002773824021 + 30 +0.0 + 10 +14.76994295311371 + 20 +-25.16221036385625 + 30 +0.0 + 10 +14.76994295311371 + 20 +-25.16221036385625 + 30 +0.0 + 10 +14.76994295311371 + 20 +-25.16221036385625 + 30 +0.0 + 10 +14.78072135640691 + 20 +-26.61016768519903 + 30 +0.0 + 10 +14.78072135640691 + 20 +-26.61016768519903 + 30 +0.0 + 10 +14.78790768958935 + 20 +-27.09162240344957 + 30 +0.0 + 10 +14.79149975970007 + 20 +-27.57667028829133 + 30 +0.0 + 10 +14.79868609288254 + 20 +-28.05812500654179 + 30 +0.0 + 10 +14.79868609288254 + 20 +-28.05812500654179 + 30 +0.0 + 10 +14.79868609288254 + 20 +-28.05812500654179 + 30 +0.0 + 10 +14.79868609288254 + 20 +-28.05812500654179 + 30 +0.0 + 10 +14.79868609288254 + 20 +-28.11201921596868 + 30 +0.0 + 10 +14.84180189901619 + 20 +-28.15513502210236 + 30 +0.0 + 10 +14.89569610844309 + 20 +-28.15513502210236 + 30 +0.0 + 10 +14.89569610844309 + 20 +-28.15513502210236 + 30 +0.0 + 10 +19.35095075439679 + 20 +-28.20543606493798 + 30 +0.0 + 10 +19.35095075439679 + 20 +-28.20543606493798 + 30 +0.0 + 10 +19.35095075439679 + 20 +-28.20543606493798 + 30 +0.0 + 10 +20.09469128307965 + 20 +-28.2126223981204 + 30 +0.0 + 10 +20.09469128307965 + 20 +-28.2126223981204 + 30 +0.0 + 10 +20.09469128307965 + 20 +-28.2126223981204 + 30 +0.0 + 10 +20.46476441588523 + 20 +-28.21621556471167 + 30 +0.0 + 10 +20.46476441588523 + 20 +-28.21621556471167 + 30 +0.0 + 10 +20.58692440462333 + 20 +-28.21621556471167 + 30 +0.0 + 10 +20.71267755995271 + 20 +-28.21621556471167 + 30 +0.0 + 10 +20.83483754869076 + 20 +-28.21621556471167 + 30 +0.0 + 10 +20.83483754869076 + 20 +-28.21621556471167 + 30 +0.0 + 10 +20.83843071528203 + 20 +-28.21621556471167 + 30 +0.0 + 10 +20.83843071528203 + 20 +-28.21621556471167 + 30 +0.0 + 10 +20.83843071528203 + 20 +-28.21621556471167 + 30 +0.0 + 10 +20.83843071528203 + 20 +-28.21621556471167 + 30 +0.0 + 10 +20.83843071528203 + 20 +-28.21621556471167 + 30 +0.0 + 10 +20.92466123106889 + 20 +-28.21621556471167 + 30 +0.0 + 10 +20.99292701038017 + 20 +-28.14435661880919 + 30 +0.0 + 10 +20.99292701038017 + 20 +-28.05812610302231 + 30 +0.0 + 10 +20.99292701038017 + 20 +-28.05812610302231 + 30 +0.0 + 10 +20.96059070402018 + 20 +-25.08316508477133 + 30 +0.0 + 10 +20.96059070402018 + 20 +-25.08316508477133 + 30 +0.0 + 10 +20.96059070402018 + 20 +-25.08316508477133 + 30 +0.0 + 10 +21.63606435691127 + 20 +-24.68794088230768 + 30 +0.0 + 10 +21.63606435691127 + 20 +-24.68794088230768 + 30 +0.0 + 10 +21.65043592679563 + 20 +-24.74543154776726 + 30 +0.0 + 10 +21.65043592679563 + 20 +-24.80291782730483 + 30 +0.0 + 10 +21.65402909338682 + 20 +-24.86040520332295 + 30 +0.0 + 0 +SPLINE + 5 +43 +330 +1F +100 +AcDbEntity + 8 +Layer 1 +370 + 0 +100 +AcDbSpline +210 +0.0 +220 +0.0 +230 +1.0 + 70 + 11 + 71 + 3 + 72 + 215 + 73 + 211 + 74 + 0 + 42 +0.0000000001 + 43 +0.0000000001 + 12 +1.0 + 22 +0.0 + 32 +0.0 + 13 +1.0 + 23 +0.0 + 33 +0.0 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +1.0 + 40 +1.0 + 40 +1.0 + 40 +2.0 + 40 +2.0 + 40 +2.0 + 40 +3.0 + 40 +3.0 + 40 +3.0 + 40 +4.0 + 40 +4.0 + 40 +4.0 + 40 +5.0 + 40 +5.0 + 40 +5.0 + 40 +6.0 + 40 +6.0 + 40 +6.0 + 40 +7.0 + 40 +7.0 + 40 +7.0 + 40 +8.0 + 40 +8.0 + 40 +8.0 + 40 +9.0 + 40 +9.0 + 40 +9.0 + 40 +10.0 + 40 +10.0 + 40 +10.0 + 40 +11.0 + 40 +11.0 + 40 +11.0 + 40 +12.0 + 40 +12.0 + 40 +12.0 + 40 +13.0 + 40 +13.0 + 40 +13.0 + 40 +14.0 + 40 +14.0 + 40 +14.0 + 40 +15.0 + 40 +15.0 + 40 +15.0 + 40 +16.0 + 40 +16.0 + 40 +16.0 + 40 +17.0 + 40 +17.0 + 40 +17.0 + 40 +18.0 + 40 +18.0 + 40 +18.0 + 40 +19.0 + 40 +19.0 + 40 +19.0 + 40 +20.0 + 40 +20.0 + 40 +20.0 + 40 +21.0 + 40 +21.0 + 40 +21.0 + 40 +22.0 + 40 +22.0 + 40 +22.0 + 40 +23.0 + 40 +23.0 + 40 +23.0 + 40 +24.0 + 40 +24.0 + 40 +24.0 + 40 +25.0 + 40 +25.0 + 40 +25.0 + 40 +26.0 + 40 +26.0 + 40 +26.0 + 40 +27.0 + 40 +27.0 + 40 +27.0 + 40 +28.0 + 40 +28.0 + 40 +28.0 + 40 +29.0 + 40 +29.0 + 40 +29.0 + 40 +30.0 + 40 +30.0 + 40 +30.0 + 40 +31.0 + 40 +31.0 + 40 +31.0 + 40 +32.0 + 40 +32.0 + 40 +32.0 + 40 +33.0 + 40 +33.0 + 40 +33.0 + 40 +34.0 + 40 +34.0 + 40 +34.0 + 40 +35.0 + 40 +35.0 + 40 +35.0 + 40 +36.0 + 40 +36.0 + 40 +36.0 + 40 +37.0 + 40 +37.0 + 40 +37.0 + 40 +38.0 + 40 +38.0 + 40 +38.0 + 40 +39.0 + 40 +39.0 + 40 +39.0 + 40 +40.0 + 40 +40.0 + 40 +40.0 + 40 +41.0 + 40 +41.0 + 40 +41.0 + 40 +42.0 + 40 +42.0 + 40 +42.0 + 40 +43.0 + 40 +43.0 + 40 +43.0 + 40 +44.0 + 40 +44.0 + 40 +44.0 + 40 +45.0 + 40 +45.0 + 40 +45.0 + 40 +46.0 + 40 +46.0 + 40 +46.0 + 40 +47.0 + 40 +47.0 + 40 +47.0 + 40 +48.0 + 40 +48.0 + 40 +48.0 + 40 +49.0 + 40 +49.0 + 40 +49.0 + 40 +50.0 + 40 +50.0 + 40 +50.0 + 40 +51.0 + 40 +51.0 + 40 +51.0 + 40 +52.0 + 40 +52.0 + 40 +52.0 + 40 +53.0 + 40 +53.0 + 40 +53.0 + 40 +54.0 + 40 +54.0 + 40 +54.0 + 40 +55.0 + 40 +55.0 + 40 +55.0 + 40 +56.0 + 40 +56.0 + 40 +56.0 + 40 +57.0 + 40 +57.0 + 40 +57.0 + 40 +58.0 + 40 +58.0 + 40 +58.0 + 40 +59.0 + 40 +59.0 + 40 +59.0 + 40 +60.0 + 40 +60.0 + 40 +60.0 + 40 +61.0 + 40 +61.0 + 40 +61.0 + 40 +62.0 + 40 +62.0 + 40 +62.0 + 40 +63.0 + 40 +63.0 + 40 +63.0 + 40 +64.0 + 40 +64.0 + 40 +64.0 + 40 +65.0 + 40 +65.0 + 40 +65.0 + 40 +66.0 + 40 +66.0 + 40 +66.0 + 40 +67.0 + 40 +67.0 + 40 +67.0 + 40 +68.0 + 40 +68.0 + 40 +68.0 + 40 +69.0 + 40 +69.0 + 40 +69.0 + 40 +70.0 + 40 +70.0 + 40 +70.0 + 40 +70.0 + 10 +17.38920021548251 + 20 +-25.79816466664644 + 30 +0.0 + 10 +17.27063339333563 + 20 +-25.64366837154827 + 30 +0.0 + 10 +17.15206547470823 + 20 +-25.48917097996966 + 30 +0.0 + 10 +17.03349865256137 + 20 +-25.33826785146274 + 30 +0.0 + 10 +17.03349865256137 + 20 +-25.33826785146274 + 30 +0.0 + 10 +16.62390288021334 + 20 +-24.8101041604873 + 30 +0.0 + 10 +16.62390288021334 + 20 +-24.8101041604873 + 30 +0.0 + 10 +16.62390288021334 + 20 +-24.8101041604873 + 30 +0.0 + 10 +16.51970762795084 + 20 +-24.68075783856673 + 30 +0.0 + 10 +16.51970762795084 + 20 +-24.68075783856673 + 30 +0.0 + 10 +16.50892922465765 + 20 +-24.66997943527359 + 30 +0.0 + 10 +16.50533605806643 + 20 +-24.6591999354999 + 30 +0.0 + 10 +16.49096448818204 + 20 +-24.64842153220674 + 30 +0.0 + 10 +16.48018608488888 + 20 +-24.63404996232233 + 30 +0.0 + 10 +16.46940658511519 + 20 +-24.62327046254866 + 30 +0.0 + 10 +16.45503501523076 + 20 +-24.60889889266425 + 30 +0.0 + 10 +16.45503501523076 + 20 +-24.60889889266425 + 30 +0.0 + 10 +16.41551237568829 + 20 +-24.57296941971302 + 30 +0.0 + 10 +16.41551237568829 + 20 +-24.57296941971302 + 30 +0.0 + 10 +16.40114080580391 + 20 +-24.56219101641985 + 30 +0.0 + 10 +16.38676923591955 + 20 +-24.55141151664619 + 30 +0.0 + 10 +16.37239656955464 + 20 +-24.540633113353 + 30 +0.0 + 10 +16.25382974740776 + 20 +-24.45799576415735 + 30 +0.0 + 10 +16.10651868901156 + 20 +-24.41847312461487 + 30 +0.0 + 10 +15.96639396379783 + 20 +-24.4364378610905 + 30 +0.0 + 10 +15.89453501789535 + 20 +-24.44362419427297 + 30 +0.0 + 10 +15.82267607199287 + 20 +-24.46518100085932 + 30 +0.0 + 10 +15.75800345927284 + 20 +-24.49751840369981 + 30 +0.0 + 10 +15.74003872279721 + 20 +-24.50829680699301 + 30 +0.0 + 10 +15.7220739863216 + 20 +-24.51548314017544 + 30 +0.0 + 10 +15.70410924984599 + 20 +-24.52626154346864 + 30 +0.0 + 10 +15.69333084655283 + 20 +-24.53344787665105 + 30 +0.0 + 10 +15.68973767996158 + 20 +-24.53344787665105 + 30 +0.0 + 10 +15.68614451337036 + 20 +-24.540633113353 + 30 +0.0 + 10 +15.68614451337036 + 20 +-24.540633113353 + 30 +0.0 + 10 +15.6681797768947 + 20 +-24.55141151664619 + 30 +0.0 + 10 +15.6681797768947 + 20 +-24.55141151664619 + 30 +0.0 + 10 +15.6681797768947 + 20 +-24.55141151664619 + 30 +0.0 + 10 +15.66099344371226 + 20 +-24.55859784982865 + 30 +0.0 + 10 +15.66099344371226 + 20 +-24.55859784982865 + 30 +0.0 + 10 +15.66099344371226 + 20 +-24.55859784982865 + 30 +0.0 + 10 +15.65380711052985 + 20 +-24.56578418301109 + 30 +0.0 + 10 +15.65380711052985 + 20 +-24.56578418301109 + 30 +0.0 + 10 +15.65380711052985 + 20 +-24.56578418301109 + 30 +0.0 + 10 +15.6502139439386 + 20 +-24.56937734960229 + 30 +0.0 + 10 +15.6502139439386 + 20 +-24.56937734960229 + 30 +0.0 + 10 +15.6502139439386 + 20 +-24.56937734960229 + 30 +0.0 + 10 +15.64662077734743 + 20 +-24.57297051619354 + 30 +0.0 + 10 +15.64662077734743 + 20 +-24.57297051619354 + 30 +0.0 + 10 +15.64662077734743 + 20 +-24.57297051619354 + 30 +0.0 + 10 +15.62506287428055 + 20 +-24.59093525266912 + 30 +0.0 + 10 +15.62506287428055 + 20 +-24.59093525266912 + 30 +0.0 + 10 +15.61069130439614 + 20 +-24.60171365596231 + 30 +0.0 + 10 +15.59991180462248 + 20 +-24.61608632232724 + 30 +0.0 + 10 +15.58554023473807 + 20 +-24.6304578922116 + 30 +0.0 + 10 +15.48493814906682 + 20 +-24.73824631106532 + 30 +0.0 + 10 +15.42745077304873 + 20 +-24.87837103627905 + 30 +0.0 + 10 +15.42385760645751 + 20 +-25.02568209467525 + 30 +0.0 + 10 +15.42385760645751 + 20 +-25.0975410405777 + 30 +0.0 + 10 +15.4346360097507 + 20 +-25.17299315307145 + 30 +0.0 + 10 +15.45619391281753 + 20 +-25.24125893238266 + 30 +0.0 + 10 +15.45978707940874 + 20 +-25.25922366885831 + 30 +0.0 + 10 +15.47056548270189 + 20 +-25.27718840533394 + 30 +0.0 + 10 +15.47775181588435 + 20 +-25.29155997521831 + 30 +0.0 + 10 +15.47775181588435 + 20 +-25.29155997521831 + 30 +0.0 + 10 +15.48853021917754 + 20 +-25.31671104487638 + 30 +0.0 + 10 +15.48853021917754 + 20 +-25.31671104487638 + 30 +0.0 + 10 +15.48853021917754 + 20 +-25.31671104487638 + 30 +0.0 + 10 +15.50290178906195 + 20 +-25.34186211453445 + 30 +0.0 + 10 +15.50290178906195 + 20 +-25.34186211453445 + 30 +0.0 + 10 +15.50290178906195 + 20 +-25.34186211453445 + 30 +0.0 + 10 +15.51727335894637 + 20 +-25.36701318419255 + 30 +0.0 + 10 +15.51727335894637 + 20 +-25.36701318419255 + 30 +0.0 + 10 +15.52445969212878 + 20 +-25.37779158748572 + 30 +0.0 + 10 +15.52445969212878 + 20 +-25.37779158748572 + 30 +0.0 + 10 +15.53164492883073 + 20 +-25.38497792066816 + 30 +0.0 + 10 +15.53164492883073 + 20 +-25.38497792066816 + 30 +0.0 + 10 +15.54242333212392 + 20 +-25.40294265714374 + 30 +0.0 + 10 +15.54242333212392 + 20 +-25.40294265714374 + 30 +0.0 + 10 +15.54242333212392 + 20 +-25.40294265714374 + 30 +0.0 + 10 +15.54601649871509 + 20 +-25.40653582373501 + 30 +0.0 + 10 +15.54601649871509 + 20 +-25.40653582373501 + 30 +0.0 + 10 +15.54601649871509 + 20 +-25.40653582373501 + 30 +0.0 + 10 +15.54601649871509 + 20 +-25.4101289903262 + 30 +0.0 + 10 +15.54601649871509 + 20 +-25.4101289903262 + 30 +0.0 + 10 +15.54601649871509 + 20 +-25.4101289903262 + 30 +0.0 + 10 +15.55320283189755 + 20 +-25.41731532350867 + 30 +0.0 + 10 +15.55320283189755 + 20 +-25.41731532350867 + 30 +0.0 + 10 +15.55320283189755 + 20 +-25.41731532350867 + 30 +0.0 + 10 +15.56038916508002 + 20 +-25.42450165669111 + 30 +0.0 + 10 +15.56038916508002 + 20 +-25.42450165669111 + 30 +0.0 + 10 +15.56038916508002 + 20 +-25.42450165669111 + 30 +0.0 + 10 +15.76877966960501 + 20 +-25.68678637064295 + 30 +0.0 + 10 +15.76877966960501 + 20 +-25.68678637064295 + 30 +0.0 + 10 +15.76877966960501 + 20 +-25.68678637064295 + 30 +0.0 + 10 +16.18556177513549 + 20 +-26.20776372843594 + 30 +0.0 + 10 +16.18556177513549 + 20 +-26.20776372843594 + 30 +0.0 + 10 +16.18556177513549 + 20 +-26.20776372843594 + 30 +0.0 + 10 +16.60234388066599 + 20 +-26.72874108622894 + 30 +0.0 + 10 +16.60234388066599 + 20 +-26.72874108622894 + 30 +0.0 + 10 +16.60234388066599 + 20 +-26.72874108622894 + 30 +0.0 + 10 +16.70653913292848 + 20 +-26.85808740814948 + 30 +0.0 + 10 +16.70653913292848 + 20 +-26.85808740814948 + 30 +0.0 + 10 +16.70653913292848 + 20 +-26.85808740814948 + 30 +0.0 + 10 +16.76043334235533 + 20 +-26.92276002086952 + 30 +0.0 + 10 +16.76043334235533 + 20 +-26.92276002086952 + 30 +0.0 + 10 +16.78199124542216 + 20 +-26.94791109052757 + 30 +0.0 + 10 +16.80714121859976 + 20 +-26.97665423029639 + 30 +0.0 + 10 +16.83588545484905 + 20 +-27.00180529995441 + 30 +0.0 + 10 +16.89337283086711 + 20 +-27.05210634279006 + 30 +0.0 + 10 +16.95804544358715 + 20 +-27.09162898233254 + 30 +0.0 + 10 +17.02631122289843 + 20 +-27.11678005199059 + 30 +0.0 + 10 +17.06224069584965 + 20 +-27.13115162187501 + 30 +0.0 + 10 +17.09817016880091 + 20 +-27.13833795505747 + 30 +0.0 + 10 +17.13409964175212 + 20 +-27.14552319175937 + 30 +0.0 + 10 +17.15206437822776 + 20 +-27.14911635835066 + 30 +0.0 + 10 +17.17002911470336 + 20 +-27.15270952494183 + 30 +0.0 + 10 +17.19158701777024 + 20 +-27.15270952494183 + 30 +0.0 + 10 +17.19158701777024 + 20 +-27.15270952494183 + 30 +0.0 + 10 +17.2059585876546 + 20 +-27.15270952494183 + 30 +0.0 + 10 +17.2059585876546 + 20 +-27.15270952494183 + 30 +0.0 + 10 +17.21314492083707 + 20 +-27.15270952494183 + 30 +0.0 + 10 +17.21673699094779 + 20 +-27.15270952494183 + 30 +0.0 + 10 +17.22033015753901 + 20 +-27.15270952494183 + 30 +0.0 + 10 +17.22033015753901 + 20 +-27.15270952494183 + 30 +0.0 + 10 +17.24188806060584 + 20 +-27.15270952494183 + 30 +0.0 + 10 +17.24188806060584 + 20 +-27.15270952494183 + 30 +0.0 + 10 +17.24188806060584 + 20 +-27.15270952494183 + 30 +0.0 + 10 +17.24548122719706 + 20 +-27.15270952494183 + 30 +0.0 + 10 +17.24548122719706 + 20 +-27.15270952494183 + 30 +0.0 + 10 +17.34967647945955 + 20 +-27.15270952494183 + 30 +0.0 + 10 +17.45746489831329 + 20 +-27.12396638517306 + 30 +0.0 + 10 +17.54728858069138 + 20 +-27.07366424585689 + 30 +0.0 + 10 +17.54728858069138 + 20 +-27.07366424585689 + 30 +0.0 + 10 +17.75208701510564 + 20 +-26.95509742371003 + 30 +0.0 + 10 +17.75208701510564 + 20 +-26.95509742371003 + 30 +0.0 + 10 +17.75208701510564 + 20 +-26.95509742371003 + 30 +0.0 + 10 +18.15808962086246 + 20 +-26.71796268293575 + 30 +0.0 + 10 +18.15808962086246 + 20 +-26.71796268293575 + 30 +0.0 + 10 +18.15808962086246 + 20 +-26.71796268293575 + 30 +0.0 + 10 +18.9736890954478 + 20 +-26.24369320138718 + 30 +0.0 + 10 +18.9736890954478 + 20 +-26.24369320138718 + 30 +0.0 + 10 +18.9736890954478 + 20 +-26.24369320138718 + 30 +0.0 + 10 +20.60488694813797 + 20 +-25.29515533477052 + 30 +0.0 + 10 +20.60488694813797 + 20 +-25.29515533477052 + 30 +0.0 + 10 +20.60488694813797 + 20 +-25.29515533477052 + 30 +0.0 + 10 +20.70908220040042 + 20 +-25.23407479216121 + 30 +0.0 + 10 +20.70908220040042 + 20 +-25.23407479216121 + 30 +0.0 + 10 +20.70908220040042 + 20 +-25.23407479216121 + 30 +0.0 + 10 +20.68033906063167 + 20 +-27.90363529032669 + 30 +0.0 + 10 +20.68033906063167 + 20 +-27.90363529032669 + 30 +0.0 + 10 +20.60848011472919 + 20 +-27.90363529032669 + 30 +0.0 + 10 +20.53662116882672 + 20 +-27.90363529032669 + 30 +0.0 + 10 +20.46476222292424 + 20 +-27.90363529032669 + 30 +0.0 + 10 +20.46476222292424 + 20 +-27.90363529032669 + 30 +0.0 + 10 +20.09468909011866 + 20 +-27.90722845691789 + 30 +0.0 + 10 +20.09468909011866 + 20 +-27.90722845691789 + 30 +0.0 + 10 +20.09468909011866 + 20 +-27.90722845691789 + 30 +0.0 + 10 +19.35094856143577 + 20 +-27.91441479010033 + 30 +0.0 + 10 +19.35094856143577 + 20 +-27.91441479010033 + 30 +0.0 + 10 +19.35094856143577 + 20 +-27.91441479010033 + 30 +0.0 + 10 +17.8670617671418 + 20 +-27.93237952657596 + 30 +0.0 + 10 +17.8670617671418 + 20 +-27.93237952657596 + 30 +0.0 + 10 +17.8670617671418 + 20 +-27.93237952657596 + 30 +0.0 + 10 +14.99629709763389 + 20 +-27.96471583293598 + 30 +0.0 + 10 +14.99629709763389 + 20 +-27.96471583293598 + 30 +0.0 + 10 +15.00348343081628 + 20 +-27.51559742104544 + 30 +0.0 + 10 +15.00707550092703 + 20 +-27.06647900915499 + 30 +0.0 + 10 +15.01426183410947 + 20 +-26.61736059726445 + 30 +0.0 + 10 +15.01426183410947 + 20 +-26.61736059726445 + 30 +0.0 + 10 +15.02504023740261 + 20 +-25.16940327592172 + 30 +0.0 + 10 +15.02504023740261 + 20 +-25.16940327592172 + 30 +0.0 + 10 +15.02504023740261 + 20 +-25.16940327592172 + 30 +0.0 + 10 +15.02863340399388 + 20 +-24.44722065030568 + 30 +0.0 + 10 +15.02863340399388 + 20 +-24.44722065030568 + 30 +0.0 + 10 +15.02863340399388 + 20 +-24.44722065030568 + 30 +0.0 + 10 +15.02863340399388 + 20 +-23.72503802468966 + 30 +0.0 + 10 +15.02863340399388 + 20 +-23.72503802468966 + 30 +0.0 + 10 +15.02863340399388 + 20 +-23.72503802468966 + 30 +0.0 + 10 +15.03222657058508 + 20 +-22.41720542856061 + 30 +0.0 + 10 +15.03222657058508 + 20 +-22.41720542856061 + 30 +0.0 + 10 +15.03222657058508 + 20 +-22.41720542856061 + 30 +0.0 + 10 +15.63584127757374 + 20 +-22.41720542856061 + 30 +0.0 + 10 +15.63584127757374 + 20 +-22.41720542856061 + 30 +0.0 + 10 +15.63584127757374 + 20 +-22.41720542856061 + 30 +0.0 + 10 +16.37958180625658 + 20 +-22.41361226196942 + 30 +0.0 + 10 +16.37958180625658 + 20 +-22.41361226196942 + 30 +0.0 + 10 +16.37958180625658 + 20 +-22.41361226196942 + 30 +0.0 + 10 +17.8634686005506 + 20 +-22.41001909537815 + 30 +0.0 + 10 +17.8634686005506 + 20 +-22.41001909537815 + 30 +0.0 + 10 +17.8634686005506 + 20 +-22.41001909537815 + 30 +0.0 + 10 +19.34735539484457 + 20 +-22.39564752549379 + 30 +0.0 + 10 +19.34735539484457 + 20 +-22.39564752549379 + 30 +0.0 + 10 +19.34735539484457 + 20 +-22.39564752549379 + 30 +0.0 + 10 +20.09109592352744 + 20 +-22.3848691222006 + 30 +0.0 + 10 +20.09109592352744 + 20 +-22.3848691222006 + 30 +0.0 + 10 +20.3066727612349 + 20 +-22.38127595560938 + 30 +0.0 + 10 +20.51865643235106 + 20 +-22.37768278901816 + 30 +0.0 + 10 +20.73423327005851 + 20 +-22.37409071890743 + 30 +0.0 + 10 +20.73423327005851 + 20 +-22.37409071890743 + 30 +0.0 + 10 +20.71986170017415 + 20 +-23.83282754002385 + 30 +0.0 + 10 +20.71986170017415 + 20 +-23.83282754002385 + 30 +0.0 + 10 +20.71986170017415 + 20 +-23.83282754002385 + 30 +0.0 + 10 +20.54740066860035 + 20 +-23.93342962569515 + 30 +0.0 + 10 +20.54740066860035 + 20 +-23.93342962569515 + 30 +0.0 + 10 +20.54740066860035 + 20 +-23.93342962569515 + 30 +0.0 + 10 +18.92338805261213 + 20 +-24.89274699208545 + 30 +0.0 + 10 +18.92338805261213 + 20 +-24.89274699208545 + 30 +0.0 + 10 +18.92338805261213 + 20 +-24.89274699208545 + 30 +0.0 + 10 +17.38920021548251 + 20 +-25.79816466664644 + 30 +0.0 + 10 +17.38920021548251 + 20 +-25.79816466664644 + 30 +0.0 + 0 +SPLINE + 5 +44 +330 +1F +100 +AcDbEntity + 8 +Layer 1 +370 + 0 +100 +AcDbSpline +210 +0.0 +220 +0.0 +230 +1.0 + 70 + 11 + 71 + 3 + 72 + 218 + 73 + 214 + 74 + 0 + 42 +0.0000000001 + 43 +0.0000000001 + 12 +1.0 + 22 +0.0 + 32 +0.0 + 13 +1.0 + 23 +0.0 + 33 +0.0 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +1.0 + 40 +1.0 + 40 +1.0 + 40 +2.0 + 40 +2.0 + 40 +2.0 + 40 +3.0 + 40 +3.0 + 40 +3.0 + 40 +4.0 + 40 +4.0 + 40 +4.0 + 40 +5.0 + 40 +5.0 + 40 +5.0 + 40 +6.0 + 40 +6.0 + 40 +6.0 + 40 +7.0 + 40 +7.0 + 40 +7.0 + 40 +8.0 + 40 +8.0 + 40 +8.0 + 40 +9.0 + 40 +9.0 + 40 +9.0 + 40 +10.0 + 40 +10.0 + 40 +10.0 + 40 +11.0 + 40 +11.0 + 40 +11.0 + 40 +12.0 + 40 +12.0 + 40 +12.0 + 40 +13.0 + 40 +13.0 + 40 +13.0 + 40 +14.0 + 40 +14.0 + 40 +14.0 + 40 +15.0 + 40 +15.0 + 40 +15.0 + 40 +16.0 + 40 +16.0 + 40 +16.0 + 40 +17.0 + 40 +17.0 + 40 +17.0 + 40 +18.0 + 40 +18.0 + 40 +18.0 + 40 +19.0 + 40 +19.0 + 40 +19.0 + 40 +20.0 + 40 +20.0 + 40 +20.0 + 40 +21.0 + 40 +21.0 + 40 +21.0 + 40 +22.0 + 40 +22.0 + 40 +22.0 + 40 +23.0 + 40 +23.0 + 40 +23.0 + 40 +24.0 + 40 +24.0 + 40 +24.0 + 40 +25.0 + 40 +25.0 + 40 +25.0 + 40 +26.0 + 40 +26.0 + 40 +26.0 + 40 +27.0 + 40 +27.0 + 40 +27.0 + 40 +28.0 + 40 +28.0 + 40 +28.0 + 40 +29.0 + 40 +29.0 + 40 +29.0 + 40 +30.0 + 40 +30.0 + 40 +30.0 + 40 +31.0 + 40 +31.0 + 40 +31.0 + 40 +32.0 + 40 +32.0 + 40 +32.0 + 40 +33.0 + 40 +33.0 + 40 +33.0 + 40 +34.0 + 40 +34.0 + 40 +34.0 + 40 +35.0 + 40 +35.0 + 40 +35.0 + 40 +36.0 + 40 +36.0 + 40 +36.0 + 40 +37.0 + 40 +37.0 + 40 +37.0 + 40 +38.0 + 40 +38.0 + 40 +38.0 + 40 +39.0 + 40 +39.0 + 40 +39.0 + 40 +40.0 + 40 +40.0 + 40 +40.0 + 40 +41.0 + 40 +41.0 + 40 +41.0 + 40 +42.0 + 40 +42.0 + 40 +42.0 + 40 +43.0 + 40 +43.0 + 40 +43.0 + 40 +44.0 + 40 +44.0 + 40 +44.0 + 40 +45.0 + 40 +45.0 + 40 +45.0 + 40 +46.0 + 40 +46.0 + 40 +46.0 + 40 +47.0 + 40 +47.0 + 40 +47.0 + 40 +48.0 + 40 +48.0 + 40 +48.0 + 40 +49.0 + 40 +49.0 + 40 +49.0 + 40 +50.0 + 40 +50.0 + 40 +50.0 + 40 +51.0 + 40 +51.0 + 40 +51.0 + 40 +52.0 + 40 +52.0 + 40 +52.0 + 40 +53.0 + 40 +53.0 + 40 +53.0 + 40 +54.0 + 40 +54.0 + 40 +54.0 + 40 +55.0 + 40 +55.0 + 40 +55.0 + 40 +56.0 + 40 +56.0 + 40 +56.0 + 40 +57.0 + 40 +57.0 + 40 +57.0 + 40 +58.0 + 40 +58.0 + 40 +58.0 + 40 +59.0 + 40 +59.0 + 40 +59.0 + 40 +60.0 + 40 +60.0 + 40 +60.0 + 40 +61.0 + 40 +61.0 + 40 +61.0 + 40 +62.0 + 40 +62.0 + 40 +62.0 + 40 +63.0 + 40 +63.0 + 40 +63.0 + 40 +64.0 + 40 +64.0 + 40 +64.0 + 40 +65.0 + 40 +65.0 + 40 +65.0 + 40 +66.0 + 40 +66.0 + 40 +66.0 + 40 +67.0 + 40 +67.0 + 40 +67.0 + 40 +68.0 + 40 +68.0 + 40 +68.0 + 40 +69.0 + 40 +69.0 + 40 +69.0 + 40 +70.0 + 40 +70.0 + 40 +70.0 + 40 +71.0 + 40 +71.0 + 40 +71.0 + 40 +71.0 + 10 +23.52954802003377 + 20 +-23.12501209837023 + 30 +0.0 + 10 +23.5187696167406 + 20 +-23.17890630779713 + 30 +0.0 + 10 +23.49721061719331 + 20 +-23.22920735063273 + 30 +0.0 + 10 +23.46128114424202 + 20 +-23.27232315676643 + 30 +0.0 + 10 +23.44331640776636 + 20 +-23.29388105983331 + 30 +0.0 + 10 +23.42535167129083 + 20 +-23.31184579630892 + 30 +0.0 + 10 +23.40379486470445 + 20 +-23.32621736619333 + 30 +0.0 + 10 +23.39301646141126 + 20 +-23.33340369937574 + 30 +0.0 + 10 +23.38223805811809 + 20 +-23.34058893607769 + 30 +0.0 + 10 +23.36786539175316 + 20 +-23.34777526926016 + 30 +0.0 + 10 +23.36786539175316 + 20 +-23.34777526926016 + 30 +0.0 + 10 +23.31756325243704 + 20 +-23.37651840902893 + 30 +0.0 + 10 +23.31756325243704 + 20 +-23.37651840902893 + 30 +0.0 + 10 +23.31756325243704 + 20 +-23.37651840902893 + 30 +0.0 + 10 +22.90796638360851 + 20 +-23.61005998321199 + 30 +0.0 + 10 +22.90796638360851 + 20 +-23.61005998321199 + 30 +0.0 + 10 +22.90796638360851 + 20 +-23.61005998321199 + 30 +0.0 + 10 +22.09236690902316 + 20 +-24.08073629816936 + 30 +0.0 + 10 +22.09236690902316 + 20 +-24.08073629816936 + 30 +0.0 + 10 +22.09236690902316 + 20 +-24.08073629816936 + 30 +0.0 + 10 +20.46117015281349 + 20 +-25.02927416478597 + 30 +0.0 + 10 +20.46117015281349 + 20 +-25.02927416478597 + 30 +0.0 + 10 +20.46117015281349 + 20 +-25.02927416478597 + 30 +0.0 + 10 +18.82997230012329 + 20 +-25.97421886481142 + 30 +0.0 + 10 +18.82997230012329 + 20 +-25.97421886481142 + 30 +0.0 + 10 +18.82997230012329 + 20 +-25.97421886481142 + 30 +0.0 + 10 +18.01437282553803 + 20 +-26.44489517976869 + 30 +0.0 + 10 +18.01437282553803 + 20 +-26.44489517976869 + 30 +0.0 + 10 +18.01437282553803 + 20 +-26.44489517976869 + 30 +0.0 + 10 +17.60477705318997 + 20 +-26.68202992054299 + 30 +0.0 + 10 +17.60477705318997 + 20 +-26.68202992054299 + 30 +0.0 + 10 +17.60477705318997 + 20 +-26.68202992054299 + 30 +0.0 + 10 +17.40357178536692 + 20 +-26.79700467257918 + 30 +0.0 + 10 +17.40357178536692 + 20 +-26.79700467257918 + 30 +0.0 + 10 +17.35686390912249 + 20 +-26.82215574223722 + 30 +0.0 + 10 +17.30656176980637 + 20 +-26.83652731212164 + 30 +0.0 + 10 +17.25626072697072 + 20 +-26.83652731212164 + 30 +0.0 + 10 +17.25626072697072 + 20 +-26.83652731212164 + 30 +0.0 + 10 +17.23829599049511 + 20 +-26.83652731212164 + 30 +0.0 + 10 +17.23829599049511 + 20 +-26.83652731212164 + 30 +0.0 + 10 +17.23470282390389 + 20 +-26.83652731212164 + 30 +0.0 + 10 +17.2311096573127 + 20 +-26.83652731212164 + 30 +0.0 + 10 +17.2311096573127 + 20 +-26.83652731212164 + 30 +0.0 + 10 +17.2311096573127 + 20 +-26.83652731212164 + 30 +0.0 + 10 +17.22751649072143 + 20 +-26.83652731212164 + 30 +0.0 + 10 +17.22751649072143 + 20 +-26.83652731212164 + 30 +0.0 + 10 +17.22033015753901 + 20 +-26.83652731212164 + 30 +0.0 + 10 +17.20955175424584 + 20 +-26.83293414553042 + 30 +0.0 + 10 +17.2023654210634 + 20 +-26.83293414553042 + 30 +0.0 + 10 +17.18440068458777 + 20 +-26.82934097893922 + 30 +0.0 + 10 +17.16643594811214 + 20 +-26.82574781234795 + 30 +0.0 + 10 +17.14847121163653 + 20 +-26.81856257564603 + 30 +0.0 + 10 +17.11254173868529 + 20 +-26.80778417235286 + 30 +0.0 + 10 +17.08379859891647 + 20 +-26.78622626928604 + 30 +0.0 + 10 +17.05505436266718 + 20 +-26.76466836621916 + 30 +0.0 + 10 +17.04068279278282 + 20 +-26.75029679633475 + 30 +0.0 + 10 +17.02990329300916 + 20 +-26.73951729656109 + 30 +0.0 + 10 +17.01553172312475 + 20 +-26.72514572667673 + 30 +0.0 + 10 +17.01553172312475 + 20 +-26.72514572667673 + 30 +0.0 + 10 +16.96163751369785 + 20 +-26.66047311395667 + 30 +0.0 + 10 +16.96163751369785 + 20 +-26.66047311395667 + 30 +0.0 + 10 +16.96163751369785 + 20 +-26.66047311395667 + 30 +0.0 + 10 +16.85744226143536 + 20 +-26.5311267920361 + 30 +0.0 + 10 +16.85744226143536 + 20 +-26.5311267920361 + 30 +0.0 + 10 +16.85744226143536 + 20 +-26.5311267920361 + 30 +0.0 + 10 +16.43706698931368 + 20 +-26.01374260083435 + 30 +0.0 + 10 +16.43706698931368 + 20 +-26.01374260083435 + 30 +0.0 + 10 +16.43706698931368 + 20 +-26.01374260083435 + 30 +0.0 + 10 +16.00950867345103 + 20 +-25.49276414656086 + 30 +0.0 + 10 +16.00950867345103 + 20 +-25.49276414656086 + 30 +0.0 + 10 +16.00950867345103 + 20 +-25.49276414656086 + 30 +0.0 + 10 +15.7975250023348 + 20 +-25.23407150271974 + 30 +0.0 + 10 +15.7975250023348 + 20 +-25.23407150271974 + 30 +0.0 + 10 +15.7975250023348 + 20 +-25.23407150271974 + 30 +0.0 + 10 +15.79033866915233 + 20 +-25.22688516953728 + 30 +0.0 + 10 +15.79033866915233 + 20 +-25.22688516953728 + 30 +0.0 + 10 +15.79033866915233 + 20 +-25.22688516953728 + 30 +0.0 + 10 +15.78674550256111 + 20 +-25.22329200294608 + 30 +0.0 + 10 +15.78674550256111 + 20 +-25.22329200294608 + 30 +0.0 + 10 +15.78674550256111 + 20 +-25.22329200294608 + 30 +0.0 + 10 +15.77596709926795 + 20 +-25.20532726647045 + 30 +0.0 + 10 +15.77596709926795 + 20 +-25.20532726647045 + 30 +0.0 + 10 +15.77237393267675 + 20 +-25.20173409987923 + 30 +0.0 + 10 +15.76518869597481 + 20 +-25.19454886317726 + 30 +0.0 + 10 +15.76518869597481 + 20 +-25.19095569658609 + 30 +0.0 + 10 +15.76518869597481 + 20 +-25.19095569658609 + 30 +0.0 + 10 +15.76159552938356 + 20 +-25.1801772932929 + 30 +0.0 + 10 +15.76159552938356 + 20 +-25.1801772932929 + 30 +0.0 + 10 +15.76159552938356 + 20 +-25.1801772932929 + 30 +0.0 + 10 +15.7544091962011 + 20 +-25.16939888999971 + 30 +0.0 + 10 +15.7544091962011 + 20 +-25.16939888999971 + 30 +0.0 + 10 +15.7544091962011 + 20 +-25.16939888999971 + 30 +0.0 + 10 +15.74722286301865 + 20 +-25.15502732011529 + 30 +0.0 + 10 +15.74722286301865 + 20 +-25.15502732011529 + 30 +0.0 + 10 +15.74362969642746 + 20 +-25.14784098693288 + 30 +0.0 + 10 +15.74003652983624 + 20 +-25.13706258363971 + 30 +0.0 + 10 +15.73644445972546 + 20 +-25.12987625045725 + 30 +0.0 + 10 +15.7220728898411 + 20 +-25.09394677750601 + 30 +0.0 + 10 +15.71488655665864 + 20 +-25.05801730455475 + 30 +0.0 + 10 +15.71488655665864 + 20 +-25.01849466501229 + 30 +0.0 + 10 +15.71488655665864 + 20 +-24.94304255251862 + 30 +0.0 + 10 +15.74362969642746 + 20 +-24.86399836991414 + 30 +0.0 + 10 +15.79393183574358 + 20 +-24.8065109938961 + 30 +0.0 + 10 +15.80111816892605 + 20 +-24.79932466071366 + 30 +0.0 + 10 +15.80830340562794 + 20 +-24.79213942401169 + 30 +0.0 + 10 +15.81548973881041 + 20 +-24.78495309082922 + 30 +0.0 + 10 +15.81548973881041 + 20 +-24.78495309082922 + 30 +0.0 + 10 +15.8262681421036 + 20 +-24.77417468753609 + 30 +0.0 + 10 +15.8262681421036 + 20 +-24.77417468753609 + 30 +0.0 + 10 +15.8262681421036 + 20 +-24.77417468753609 + 30 +0.0 + 10 +15.82986130869482 + 20 +-24.77058152094484 + 30 +0.0 + 10 +15.82986130869482 + 20 +-24.77058152094484 + 30 +0.0 + 10 +15.82986130869482 + 20 +-24.77058152094484 + 30 +0.0 + 10 +15.82986130869482 + 20 +-24.77058152094484 + 30 +0.0 + 10 +15.82986130869482 + 20 +-24.77058152094484 + 30 +0.0 + 10 +15.82986130869482 + 20 +-24.77058152094484 + 30 +0.0 + 10 +15.82986130869482 + 20 +-24.77058152094484 + 30 +0.0 + 10 +15.82986130869482 + 20 +-24.77058152094484 + 30 +0.0 + 10 +15.82986130869482 + 20 +-24.77058152094484 + 30 +0.0 + 10 +15.83345447528604 + 20 +-24.76698835435364 + 30 +0.0 + 10 +15.83345447528604 + 20 +-24.76698835435364 + 30 +0.0 + 10 +15.83345447528604 + 20 +-24.76698835435364 + 30 +0.0 + 10 +15.85141921176169 + 20 +-24.75620995106045 + 30 +0.0 + 10 +15.85141921176169 + 20 +-24.75620995106045 + 30 +0.0 + 10 +15.85501237835287 + 20 +-24.75261678446923 + 30 +0.0 + 10 +15.86219761505484 + 20 +-24.74543154776726 + 30 +0.0 + 10 +15.86579078164606 + 20 +-24.74543154776726 + 30 +0.0 + 10 +15.87297711482852 + 20 +-24.74183838117604 + 30 +0.0 + 10 +15.88016235153044 + 20 +-24.73824521458482 + 30 +0.0 + 10 +15.88734868471288 + 20 +-24.7346531444741 + 30 +0.0 + 10 +15.92327815766412 + 20 +-24.71668840799849 + 30 +0.0 + 10 +15.95920763061536 + 20 +-24.7059100047053 + 30 +0.0 + 10 +15.99873027015784 + 20 +-24.69872367152288 + 30 +0.0 + 10 +16.07777554924279 + 20 +-24.68794526822972 + 30 +0.0 + 10 +16.16041289843841 + 20 +-24.7059100047053 + 30 +0.0 + 10 +16.22508551115849 + 20 +-24.75261788094973 + 30 +0.0 + 10 +16.23227184434091 + 20 +-24.7598042141322 + 30 +0.0 + 10 +16.24305024763407 + 20 +-24.76339628424292 + 30 +0.0 + 10 +16.25023658081649 + 20 +-24.77058261742534 + 30 +0.0 + 10 +16.25023658081649 + 20 +-24.77058261742534 + 30 +0.0 + 10 +16.27179448388337 + 20 +-24.78854735390097 + 30 +0.0 + 10 +16.27179448388337 + 20 +-24.78854735390097 + 30 +0.0 + 10 +16.27179448388337 + 20 +-24.78854735390097 + 30 +0.0 + 10 +16.29335238695019 + 20 +-24.81010525696779 + 30 +0.0 + 10 +16.29335238695019 + 20 +-24.81010525696779 + 30 +0.0 + 10 +16.30053872013266 + 20 +-24.81729159015026 + 30 +0.0 + 10 +16.3113171234258 + 20 +-24.83166316003467 + 30 +0.0 + 10 +16.31850345660827 + 20 +-24.83884839673659 + 30 +0.0 + 10 +16.31850345660827 + 20 +-24.83884839673659 + 30 +0.0 + 10 +16.42629187546203 + 20 +-24.96819471865718 + 30 +0.0 + 10 +16.42629187546203 + 20 +-24.96819471865718 + 30 +0.0 + 10 +16.42629187546203 + 20 +-24.96819471865718 + 30 +0.0 + 10 +16.85744555087684 + 20 +-25.47839367315694 + 30 +0.0 + 10 +16.85744555087684 + 20 +-25.47839367315694 + 30 +0.0 + 10 +17.0011634426818 + 20 +-25.64726263462002 + 30 +0.0 + 10 +17.14488133448678 + 20 +-25.81972366619372 + 30 +0.0 + 10 +17.28859922629176 + 20 +-25.9885926276568 + 30 +0.0 + 10 +17.28859922629176 + 20 +-25.9885926276568 + 30 +0.0 + 10 +17.28859922629176 + 20 +-25.9885926276568 + 30 +0.0 + 10 +17.28859922629176 + 20 +-25.9885926276568 + 30 +0.0 + 10 +17.28859922629176 + 20 +-25.9885926276568 + 30 +0.0 + 10 +17.28859922629176 + 20 +-25.9885926276568 + 30 +0.0 + 10 +17.28859922629176 + 20 +-25.9885926276568 + 30 +0.0 + 10 +17.31734236606058 + 20 +-26.02452210060801 + 30 +0.0 + 10 +17.37123657548742 + 20 +-26.03530050390117 + 30 +0.0 + 10 +17.41435238162108 + 20 +-26.01015053072362 + 30 +0.0 + 10 +17.41435238162108 + 20 +-26.01015053072362 + 30 +0.0 + 10 +19.05273656749371 + 20 +-25.07598423399137 + 30 +0.0 + 10 +19.05273656749371 + 20 +-25.07598423399137 + 30 +0.0 + 10 +19.05273656749371 + 20 +-25.07598423399137 + 30 +0.0 + 10 +20.69112075336635 + 20 +-24.14181793725912 + 30 +0.0 + 10 +20.69112075336635 + 20 +-24.14181793725912 + 30 +0.0 + 10 +20.69112075336635 + 20 +-24.14181793725912 + 30 +0.0 + 10 +22.32591067616729 + 20 +-23.2040584739357 + 30 +0.0 + 10 +22.32591067616729 + 20 +-23.2040584739357 + 30 +0.0 + 10 +22.32591067616729 + 20 +-23.2040584739357 + 30 +0.0 + 10 +22.73550754499572 + 20 +-22.97051689975263 + 30 +0.0 + 10 +22.73550754499572 + 20 +-22.97051689975263 + 30 +0.0 + 10 +22.73550754499572 + 20 +-22.97051689975263 + 30 +0.0 + 10 +22.94030597941001 + 20 +-22.85195007760573 + 30 +0.0 + 10 +22.94030597941001 + 20 +-22.85195007760573 + 30 +0.0 + 10 +22.94030597941001 + 20 +-22.85195007760573 + 30 +0.0 + 10 +23.04090806508131 + 20 +-22.79446270158767 + 30 +0.0 + 10 +23.04090806508131 + 20 +-22.79446270158767 + 30 +0.0 + 10 +23.04090806508131 + 20 +-22.79446270158767 + 30 +0.0 + 10 +23.04809439826375 + 20 +-22.79086953499644 + 30 +0.0 + 10 +23.04809439826375 + 20 +-22.79086953499644 + 30 +0.0 + 10 +23.04809439826375 + 20 +-22.79086953499644 + 30 +0.0 + 10 +23.04809439826375 + 20 +-22.79086953499644 + 30 +0.0 + 10 +23.04809439826375 + 20 +-22.79086953499644 + 30 +0.0 + 10 +23.04809439826375 + 20 +-22.79086953499644 + 30 +0.0 + 10 +23.04809439826375 + 20 +-22.79086953499644 + 30 +0.0 + 10 +23.04809439826375 + 20 +-22.79086953499644 + 30 +0.0 + 10 +23.04809439826375 + 20 +-22.79086953499644 + 30 +0.0 + 10 +23.06246706462869 + 20 +-22.78368320181398 + 30 +0.0 + 10 +23.06246706462869 + 20 +-22.78368320181398 + 30 +0.0 + 10 +23.06246706462869 + 20 +-22.78368320181398 + 30 +0.0 + 10 +23.08761813428671 + 20 +-22.76931163192956 + 30 +0.0 + 10 +23.08761813428671 + 20 +-22.76931163192956 + 30 +0.0 + 10 +23.09121020439743 + 20 +-22.76931163192956 + 30 +0.0 + 10 +23.0983965375799 + 20 +-22.76571846533837 + 30 +0.0 + 10 +23.10199080065164 + 20 +-22.76571846533837 + 30 +0.0 + 10 +23.10558287076234 + 20 +-22.76212529874715 + 30 +0.0 + 10 +23.11276920394481 + 20 +-22.76212529874715 + 30 +0.0 + 10 +23.11995553712728 + 20 +-22.7585321321559 + 30 +0.0 + 10 +23.22415188587024 + 20 +-22.72619582579591 + 30 +0.0 + 10 +23.34631077812782 + 20 +-22.75134579897344 + 30 +0.0 + 10 +23.42535605721276 + 20 +-22.82679791146716 + 30 +0.0 + 10 +23.46487760027478 + 20 +-22.8627273844184 + 30 +0.0 + 10 +23.49721500311524 + 20 +-22.90943526066278 + 30 +0.0 + 10 +23.5151797395909 + 20 +-22.96332947008967 + 30 +0.0 + 10 +23.53314009014449 + 20 +-23.01363051292527 + 30 +0.0 + 10 +23.53673216025522 + 20 +-23.07111788894341 + 30 +0.0 + 10 +23.52954802003377 + 20 +-23.12501209837023 + 30 +0.0 + 0 +HATCH + 5 +45 +330 +1F +100 +AcDbEntity + 8 +Layer 1 + 62 + 250 +420 + 2301728 +100 +AcDbHatch + 10 +0.0 + 20 +0.0 + 30 +0.0 +210 +0.0 +220 +0.0 +230 +1.0 + 2 +SOLID + 70 + 1 + 71 + 0 + 91 + 4 + 92 + 1 + 93 + 1 + 72 + 4 + 94 + 3 + 73 + 0 + 74 + 1 + 95 + 287 + 96 + 283 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +1.0 + 40 +1.0 + 40 +1.0 + 40 +2.0 + 40 +2.0 + 40 +2.0 + 40 +3.0 + 40 +3.0 + 40 +3.0 + 40 +4.0 + 40 +4.0 + 40 +4.0 + 40 +5.0 + 40 +5.0 + 40 +5.0 + 40 +6.0 + 40 +6.0 + 40 +6.0 + 40 +7.0 + 40 +7.0 + 40 +7.0 + 40 +8.0 + 40 +8.0 + 40 +8.0 + 40 +9.0 + 40 +9.0 + 40 +9.0 + 40 +10.0 + 40 +10.0 + 40 +10.0 + 40 +11.0 + 40 +11.0 + 40 +11.0 + 40 +12.0 + 40 +12.0 + 40 +12.0 + 40 +13.0 + 40 +13.0 + 40 +13.0 + 40 +14.0 + 40 +14.0 + 40 +14.0 + 40 +15.0 + 40 +15.0 + 40 +15.0 + 40 +16.0 + 40 +16.0 + 40 +16.0 + 40 +17.0 + 40 +17.0 + 40 +17.0 + 40 +18.0 + 40 +18.0 + 40 +18.0 + 40 +19.0 + 40 +19.0 + 40 +19.0 + 40 +20.0 + 40 +20.0 + 40 +20.0 + 40 +21.0 + 40 +21.0 + 40 +21.0 + 40 +22.0 + 40 +22.0 + 40 +22.0 + 40 +23.0 + 40 +23.0 + 40 +23.0 + 40 +24.0 + 40 +24.0 + 40 +24.0 + 40 +25.0 + 40 +25.0 + 40 +25.0 + 40 +26.0 + 40 +26.0 + 40 +26.0 + 40 +27.0 + 40 +27.0 + 40 +27.0 + 40 +28.0 + 40 +28.0 + 40 +28.0 + 40 +29.0 + 40 +29.0 + 40 +29.0 + 40 +30.0 + 40 +30.0 + 40 +30.0 + 40 +31.0 + 40 +31.0 + 40 +31.0 + 40 +32.0 + 40 +32.0 + 40 +32.0 + 40 +33.0 + 40 +33.0 + 40 +33.0 + 40 +34.0 + 40 +34.0 + 40 +34.0 + 40 +35.0 + 40 +35.0 + 40 +35.0 + 40 +36.0 + 40 +36.0 + 40 +36.0 + 40 +37.0 + 40 +37.0 + 40 +37.0 + 40 +38.0 + 40 +38.0 + 40 +38.0 + 40 +39.0 + 40 +39.0 + 40 +39.0 + 40 +40.0 + 40 +40.0 + 40 +40.0 + 40 +41.0 + 40 +41.0 + 40 +41.0 + 40 +42.0 + 40 +42.0 + 40 +42.0 + 40 +43.0 + 40 +43.0 + 40 +43.0 + 40 +44.0 + 40 +44.0 + 40 +44.0 + 40 +45.0 + 40 +45.0 + 40 +45.0 + 40 +46.0 + 40 +46.0 + 40 +46.0 + 40 +47.0 + 40 +47.0 + 40 +47.0 + 40 +48.0 + 40 +48.0 + 40 +48.0 + 40 +49.0 + 40 +49.0 + 40 +49.0 + 40 +50.0 + 40 +50.0 + 40 +50.0 + 40 +51.0 + 40 +51.0 + 40 +51.0 + 40 +52.0 + 40 +52.0 + 40 +52.0 + 40 +53.0 + 40 +53.0 + 40 +53.0 + 40 +54.0 + 40 +54.0 + 40 +54.0 + 40 +55.0 + 40 +55.0 + 40 +55.0 + 40 +56.0 + 40 +56.0 + 40 +56.0 + 40 +57.0 + 40 +57.0 + 40 +57.0 + 40 +58.0 + 40 +58.0 + 40 +58.0 + 40 +59.0 + 40 +59.0 + 40 +59.0 + 40 +60.0 + 40 +60.0 + 40 +60.0 + 40 +61.0 + 40 +61.0 + 40 +61.0 + 40 +62.0 + 40 +62.0 + 40 +62.0 + 40 +63.0 + 40 +63.0 + 40 +63.0 + 40 +64.0 + 40 +64.0 + 40 +64.0 + 40 +65.0 + 40 +65.0 + 40 +65.0 + 40 +66.0 + 40 +66.0 + 40 +66.0 + 40 +67.0 + 40 +67.0 + 40 +67.0 + 40 +68.0 + 40 +68.0 + 40 +68.0 + 40 +69.0 + 40 +69.0 + 40 +69.0 + 40 +70.0 + 40 +70.0 + 40 +70.0 + 40 +71.0 + 40 +71.0 + 40 +71.0 + 40 +72.0 + 40 +72.0 + 40 +72.0 + 40 +73.0 + 40 +73.0 + 40 +73.0 + 40 +74.0 + 40 +74.0 + 40 +74.0 + 40 +75.0 + 40 +75.0 + 40 +75.0 + 40 +76.0 + 40 +76.0 + 40 +76.0 + 40 +77.0 + 40 +77.0 + 40 +77.0 + 40 +78.0 + 40 +78.0 + 40 +78.0 + 40 +79.0 + 40 +79.0 + 40 +79.0 + 40 +80.0 + 40 +80.0 + 40 +80.0 + 40 +81.0 + 40 +81.0 + 40 +81.0 + 40 +82.0 + 40 +82.0 + 40 +82.0 + 40 +83.0 + 40 +83.0 + 40 +83.0 + 40 +84.0 + 40 +84.0 + 40 +84.0 + 40 +85.0 + 40 +85.0 + 40 +85.0 + 40 +86.0 + 40 +86.0 + 40 +86.0 + 40 +87.0 + 40 +87.0 + 40 +87.0 + 40 +88.0 + 40 +88.0 + 40 +88.0 + 40 +89.0 + 40 +89.0 + 40 +89.0 + 40 +90.0 + 40 +90.0 + 40 +90.0 + 40 +91.0 + 40 +91.0 + 40 +91.0 + 40 +92.0 + 40 +92.0 + 40 +92.0 + 40 +93.0 + 40 +93.0 + 40 +93.0 + 40 +94.0 + 40 +94.0 + 40 +94.0 + 40 +94.0 + 10 +23.78464640080316 + 20 +-22.86631945452912 + 10 +23.78464640080316 + 20 +-22.86631945452912 + 10 +23.76668166432758 + 20 +-22.82679681498666 + 10 +23.76668166432758 + 20 +-22.82679681498666 + 10 +23.76668166432758 + 20 +-22.82679681498666 + 10 +23.76308959421688 + 20 +-22.81601841169347 + 10 +23.76308959421688 + 20 +-22.81601841169347 + 10 +23.76308959421688 + 20 +-22.81601841169347 + 10 +23.75949752410613 + 20 +-22.80883207851106 + 10 +23.75949752410613 + 20 +-22.80883207851106 + 10 +23.75949752410613 + 20 +-22.80883207851106 + 10 +23.75231119092369 + 20 +-22.79446050862665 + 10 +23.75231119092369 + 20 +-22.79446050862665 + 10 +23.75231119092369 + 20 +-22.79446050862665 + 10 +23.74512485774122 + 20 +-22.78008893874228 + 10 +23.74512485774122 + 20 +-22.78008893874228 + 10 +23.74512485774122 + 20 +-22.78008893874228 + 10 +23.7415327876305 + 20 +-22.77290260555982 + 10 +23.7415327876305 + 20 +-22.77290260555982 + 10 +23.7415327876305 + 20 +-22.77290260555982 + 10 +23.7415327876305 + 20 +-22.76930943896857 + 10 +23.7415327876305 + 20 +-22.76930943896857 + 10 +23.73434645444806 + 20 +-22.75493786908419 + 10 +23.73794071751978 + 20 +-22.76212310578613 + 10 +23.73434645444806 + 20 +-22.75853103567541 + 10 +23.73434645444806 + 20 +-22.75853103567541 + 10 +23.71278964786168 + 20 +-22.72619472931539 + 10 +23.71278964786168 + 20 +-22.72619472931539 + 10 +23.69841698149677 + 20 +-22.70463682624856 + 10 +23.68045224502121 + 20 +-22.68667208977291 + 10 +23.6660817716173 + 20 +-22.66511418670608 + 10 +23.64811703514164 + 20 +-22.6471494502305 + 10 +23.63015229866601 + 20 +-22.62918471375484 + 10 +23.61218756219046 + 20 +-22.61121997727921 + 10 +23.45769126709231 + 20 +-22.47468841865669 + 10 +23.23133602609166 + 20 +-22.42797944593182 + 10 +23.03731599497059 + 20 +-22.4890599885411 + 10 +23.01216492531249 + 20 +-22.49624632172357 + 10 +22.98342178554374 + 20 +-22.50702472501671 + 10 +22.95827071588565 + 20 +-22.51780312830988 + 10 +22.95827071588565 + 20 +-22.51780312830988 + 10 +22.93311964622762 + 20 +-22.53217469819431 + 10 +22.93311964622762 + 20 +-22.53217469819431 + 10 +22.93311964622762 + 20 +-22.53217469819431 + 10 +22.91874697986271 + 20 +-22.53936103137673 + 10 +22.91874697986271 + 20 +-22.53936103137673 + 10 +22.91874697986271 + 20 +-22.53936103137673 + 10 +22.90437431349778 + 20 +-22.5465473645592 + 10 +22.90437431349778 + 20 +-22.5465473645592 + 10 +22.90437431349778 + 20 +-22.5465473645592 + 10 +22.89718798031532 + 20 +-22.55014053115042 + 10 +22.89718798031532 + 20 +-22.55014053115042 + 10 +22.89718798031532 + 20 +-22.55014053115042 + 10 +22.79658589464412 + 20 +-22.61122107375973 + 10 +22.79658589464412 + 20 +-22.61122107375973 + 10 +22.79658589464412 + 20 +-22.61122107375973 + 10 +22.59178746022983 + 20 +-22.72978789590661 + 10 +22.59178746022983 + 20 +-22.72978789590661 + 10 +22.59178746022983 + 20 +-22.72978789590661 + 10 +22.18578485447304 + 20 +-22.96692263668092 + 10 +22.18578485447304 + 20 +-22.96692263668092 + 10 +22.18578485447304 + 20 +-22.96692263668092 + 10 +21.90553540404557 + 20 +-23.12860526496148 + 10 +21.90553540404557 + 20 +-23.12860526496148 + 10 +21.90553540404557 + 20 +-22.96332947008967 + 10 +21.90553540404557 + 20 +-22.79446160510714 + 10 +21.90912747415627 + 20 +-22.62559264364412 + 10 +21.90912747415627 + 20 +-22.52499055797287 + 10 +21.90912747415627 + 20 +-22.42798054241232 + 10 +21.90912747415627 + 20 +-22.32737845674102 + 10 +21.90912747415627 + 20 +-22.27707741390539 + 10 +21.90912747415627 + 20 +-22.22677637106979 + 10 +21.90912747415627 + 20 +-22.17647423175362 + 10 +21.90912747415627 + 20 +-22.15132316209555 + 10 +21.90553540404557 + 20 +-22.11898685573556 + 10 +21.90553540404557 + 20 +-22.09024371596673 + 10 +21.90553540404557 + 20 +-22.06150057619794 + 10 +21.89834907086311 + 20 +-22.03275633994867 + 10 +21.89475700075238 + 20 +-22.00401320017987 + 10 +21.85523545769037 + 20 +-21.77047162599683 + 10 +21.7258880392893 + 20 +-21.55130162169815 + 10 +21.53186910464877 + 20 +-21.40399111154223 + 10 +21.43485908908817 + 20 +-21.32853899904851 + 10 +21.32707067023451 + 20 +-21.27464478962166 + 10 +21.20850384808755 + 20 +-21.23871531667042 + 10 +21.17976070831881 + 20 +-21.23152953172818 + 10 +21.15101647206951 + 20 +-21.22434374678601 + 10 +21.11868016570952 + 20 +-21.21715741360354 + 10 +21.0863438593495 + 20 +-21.21356424701237 + 10 +21.05041438639821 + 20 +-21.20637846207018 + 10 +21.02167015014892 + 20 +-21.20278584371918 + 10 +21.02167015014892 + 20 +-21.20278584371918 + 10 +20.94262487106397 + 20 +-21.19919267712794 + 10 +20.94262487106397 + 20 +-21.19919267712794 + 10 +20.94262487106397 + 20 +-21.19919267712794 + 10 +20.85639435527714 + 20 +-21.19919267712794 + 10 +20.85639435527714 + 20 +-21.19919267712794 + 10 +20.64441068416095 + 20 +-21.19919267712794 + 10 +20.43242701304472 + 20 +-21.20278584371918 + 10 +20.21685017533726 + 20 +-21.20278584371918 + 10 +19.35454282450751 + 20 +-21.20637901031038 + 10 +18.46349233390896 + 20 +-21.22075058019482 + 10 +17.55806917694559 + 20 +-21.22793636513701 + 10 +16.65264601998211 + 20 +-21.23871531667042 + 10 +15.73285238961474 + 20 +-21.24230793502137 + 10 +14.8130576627669 + 20 +-21.24230793502137 + 10 +14.55436501892579 + 20 +-21.23871476843017 + 10 +14.29926663815642 + 20 +-21.35368952046633 + 10 +14.12680451010217 + 20 +-21.54052212192449 + 10 +14.04057399431526 + 20 +-21.6339389708938 + 10 +13.972308215004 + 20 +-21.74532055633876 + 10 +13.92919240887035 + 20 +-21.86748054507686 + 10 +13.90763450580347 + 20 +-21.92856108768617 + 10 +13.89326293591906 + 20 +-21.98964053381496 + 10 +13.88607660273662 + 20 +-22.05790631312624 + 10 +13.88607660273662 + 20 +-22.06509264630871 + 10 +13.88248343614542 + 20 +-22.07587104960185 + 10 +13.88248343614542 + 20 +-22.08305738278432 + 10 +13.88248343614542 + 20 +-22.08305738278432 + 10 +13.88248343614542 + 20 +-22.10461528585114 + 10 +13.88248343614542 + 20 +-22.10461528585114 + 10 +13.88248343614542 + 20 +-22.10461528585114 + 10 +13.87889026955418 + 20 +-22.14773109198485 + 10 +13.87889026955418 + 20 +-22.14773109198485 + 10 +13.87889026955418 + 20 +-22.14773109198485 + 10 +13.87889026955418 + 20 +-22.15850949527801 + 10 +13.87889026955418 + 20 +-22.15850949527801 + 10 +13.87889026955418 + 20 +-22.15850949527801 + 10 +13.87889026955418 + 20 +-22.1728810651624 + 10 +13.87889026955418 + 20 +-22.1728810651624 + 10 +13.87889026955418 + 20 +-22.1728810651624 + 10 +13.87889026955418 + 20 +-22.19443896822923 + 10 +13.87889026955418 + 20 +-22.19443896822923 + 10 +13.87889026955418 + 20 +-22.19443896822923 + 10 +13.87889026955418 + 20 +-22.23755477436293 + 10 +13.87889026955418 + 20 +-22.23755477436293 + 10 +13.87889026955418 + 20 +-22.69745158954662 + 10 +13.87529710296296 + 20 +-23.15375633461953 + 10 +13.86811186626101 + 20 +-23.60646791310127 + 10 +13.85733346296787 + 20 +-24.51189107006472 + 10 +13.8501471297854 + 20 +-25.40294156066327 + 10 +13.83936872649221 + 20 +-26.26524891149299 + 10 +13.83577555990099 + 20 +-26.6964025869079 + 10 +13.83218239330977 + 20 +-27.12036992914035 + 10 +13.82859032319905 + 20 +-27.5371520346708 + 10 +13.82859032319905 + 20 +-27.74554253919579 + 10 +13.82499715660785 + 20 +-27.95034097361005 + 10 +13.82499715660785 + 20 +-28.15513940802432 + 10 +13.82499715660785 + 20 +-28.27729939676247 + 10 +13.85014822626587 + 20 +-28.39586731538988 + 10 +13.89326293591906 + 20 +-28.50724780435429 + 10 +13.93637874205276 + 20 +-28.61862938979922 + 10 +14.0010513547728 + 20 +-28.7192314754705 + 10 +14.08009663385774 + 20 +-28.80546199125739 + 10 +14.15914191294264 + 20 +-28.89169250704425 + 10 +14.25255766543149 + 20 +-28.96355145294673 + 10 +14.35316084758326 + 20 +-29.01744566237362 + 10 +14.35316084758326 + 20 +-29.01744566237362 + 10 +14.43220612666821 + 20 +-29.05337513532486 + 10 +14.43220612666821 + 20 +-29.05337513532486 + 10 +14.45735719632623 + 20 +-29.064153538618 + 10 +14.48610033609505 + 20 +-29.07133987180047 + 10 +14.51484347586383 + 20 +-29.08211827509366 + 10 +14.56873768529072 + 20 +-29.09648984497802 + 10 +14.62263189471757 + 20 +-29.11086141486243 + 10 +14.68730450743758 + 20 +-29.11804774804487 + 10 +14.70167607732199 + 20 +-29.12164091463612 + 10 +14.71964081379759 + 20 +-29.12164091463612 + 10 +14.73042031357128 + 20 +-29.12523408122732 + 10 +14.73042031357128 + 20 +-29.12523408122732 + 10 +14.76634978652252 + 20 +-29.12523408122732 + 10 +14.76634978652252 + 20 +-29.12523408122732 + 10 +14.76634978652252 + 20 +-29.12523408122732 + 10 +14.80227925947373 + 20 +-29.12523408122732 + 10 +14.80227925947373 + 20 +-29.12523408122732 + 10 +14.80227925947373 + 20 +-29.12523408122732 + 10 +14.80946559265617 + 20 +-29.12523408122732 + 10 +14.80946559265617 + 20 +-29.12523408122732 + 10 +14.82743032913183 + 20 +-29.12523408122732 + 10 +14.82024399594937 + 20 +-29.12523408122732 + 10 +14.82743032913183 + 20 +-29.12523408122732 + 10 +14.82743032913183 + 20 +-29.12523408122732 + 10 +14.84539506560746 + 20 +-29.12523408122732 + 10 +14.84539506560746 + 20 +-29.12523408122732 + 10 +14.89210294185184 + 20 +-29.12523408122732 + 10 +14.93881191457679 + 20 +-29.12523408122732 + 10 +14.98911295741239 + 20 +-29.12523408122732 + 10 +15.08252980638175 + 20 +-29.12523408122732 + 10 +15.17594665535105 + 20 +-29.12164091463612 + 10 +15.26936240783986 + 20 +-29.12164091463612 + 10 +15.64302870723671 + 20 +-29.11804774804487 + 10 +16.00232343674907 + 20 +-29.11445458145365 + 10 +16.34724659637709 + 20 +-29.11086251134293 + 10 +17.03709291563311 + 20 +-29.10367617816049 + 10 +17.6694507623905 + 20 +-29.09649094145852 + 10 +18.2299507597259 + 20 +-29.08930460827605 + 10 +18.79045075706136 + 20 +-29.08211827509366 + 10 +19.27909180849429 + 20 +-29.07852620498291 + 10 +19.6815012476599 + 20 +-29.07852620498291 + 10 +20.48632122247161 + 20 +-29.07493303839169 + 10 +20.94621913413572 + 20 +-29.07493303839169 + 10 +20.94621913413572 + 20 +-29.07493303839169 + 10 +20.94621913413572 + 20 +-29.07493303839169 + 10 +20.97855544049574 + 20 +-29.07493303839169 + 10 +21.03963598310502 + 20 +-29.07133987180047 + 10 +21.10071652571433 + 20 +-29.064153538618 + 10 +21.19413227820319 + 20 +-29.04978196873364 + 10 +21.30551386364813 + 20 +-29.00307409248921 + 10 +21.41689544909309 + 20 +-28.95636621624478 + 10 +21.54624177101361 + 20 +-28.87372777056867 + 10 +21.66121542656927 + 20 +-28.7443814486481 + 10 +21.71510963599621 + 20 +-28.6761156693368 + 10 +21.76900384542306 + 20 +-28.5970703902519 + 10 +21.80852648496552 + 20 +-28.50724670787382 + 10 +21.81571281814798 + 20 +-28.48209563821572 + 10 +21.8264901249606 + 20 +-28.46053883162939 + 10 +21.83367645814307 + 20 +-28.43538776197134 + 10 +21.84086279132553 + 20 +-28.41023669231324 + 10 +21.84804912450798 + 20 +-28.38508671913567 + 10 +21.85523326472942 + 20 +-28.35993564947762 + 10 +21.86241959791189 + 20 +-28.33478457981957 + 10 +21.86601166802262 + 20 +-28.30604144005075 + 10 +21.86960593109428 + 20 +-28.27729830028195 + 10 +21.873198001205 + 20 +-28.26292673039754 + 10 +21.873198001205 + 20 +-28.24855516051318 + 10 +21.87679226427672 + 20 +-28.23418249414825 + 10 +21.87679226427672 + 20 +-28.22340409085511 + 10 +21.87679226427672 + 20 +-28.20903142449025 + 10 +21.88038433438745 + 20 +-28.19825302119706 + 10 +21.88038433438745 + 20 +-28.18747461790387 + 10 +21.88038433438745 + 20 +-28.17310195153896 + 10 +21.88397640449817 + 20 +-28.15873038165457 + 10 +21.88397640449817 + 20 +-28.15873038165457 + 10 +21.88397640449817 + 20 +-28.14435881177016 + 10 +21.88397640449817 + 20 +-28.14435881177016 + 10 +21.88397640449817 + 20 +-28.14435881177016 + 10 +21.88397640449817 + 20 +-28.13358040847702 + 10 +21.88397640449817 + 20 +-28.13358040847702 + 10 +21.88397640449817 + 20 +-28.13358040847702 + 10 +21.88397640449817 + 20 +-28.11561567200139 + 10 +21.88397640449817 + 20 +-28.11561567200139 + 10 +21.88397640449817 + 20 +-28.09046460234329 + 10 +21.88397640449817 + 20 +-28.06531462916574 + 10 +21.88397640449817 + 20 +-28.03657039291645 + 10 +21.88397640449817 + 20 +-27.9826761834896 + 10 +21.88397640449817 + 20 +-27.92878197406276 + 10 +21.88397640449817 + 20 +-27.87488776463589 + 10 +21.88397640449817 + 20 +-27.76350617919095 + 10 +21.88397640449817 + 20 +-27.64853252363529 + 10 +21.88397640449817 + 20 +-27.52637143841664 + 10 +21.88397640449817 + 20 +-27.28205146094041 + 10 +21.88756847460889 + 20 +-27.01976565050806 + 10 +21.89116273768064 + 20 +-26.73951620008059 + 10 +21.89475480779136 + 20 +-26.17901620274514 + 10 +21.90194114097383 + 20 +-25.54665725950723 + 10 +21.90553540404557 + 20 +-24.85681203673173 + 10 +21.90553540404557 + 20 +-24.75261678446923 + 10 +21.90553540404557 + 20 +-24.64482836561549 + 10 +21.90912747415627 + 20 +-24.53703994676183 + 10 +21.90912747415627 + 20 +-24.53703994676183 + 10 +22.25045746719305 + 20 +-24.33942784552998 + 10 +22.25045746719305 + 20 +-24.33942784552998 + 10 +22.25045746719305 + 20 +-24.33942784552998 + 10 +23.06605694177839 + 20 +-23.86515836398141 + 10 +23.06605694177839 + 20 +-23.86515836398141 + 10 +23.06605694177839 + 20 +-23.86515836398141 + 10 +23.47205954753518 + 20 +-23.62802362320713 + 10 +23.47205954753518 + 20 +-23.62802362320713 + 10 +23.47205954753518 + 20 +-23.62802362320713 + 10 +23.52236168685133 + 20 +-23.59928048343833 + 10 +23.52236168685133 + 20 +-23.59928048343833 + 10 +23.54032642332696 + 20 +-23.58850208014517 + 10 +23.56547749298498 + 20 +-23.57412941378023 + 10 +23.58703429957134 + 20 +-23.55616467730463 + 10 +23.62655584263335 + 20 +-23.52382837094463 + 10 +23.66607957865628 + 20 +-23.48789889799339 + 10 +23.69841478853583 + 20 +-23.44837625845091 + 10 +23.76308740125584 + 20 +-23.36933097936597 + 10 +23.80620320738952 + 20 +-23.26872889369474 + 10 +23.82416794386515 + 20 +-23.16812680802344 + 10 +23.82776220693689 + 20 +-23.06752472235217 + 10 +23.82057587375443 + 20 +-22.96332947008967 + 10 +23.78464640080316 + 20 +-22.86631945452912 + 97 + 0 + 97 + 0 + 92 + 1 + 93 + 1 + 72 + 4 + 94 + 3 + 73 + 0 + 74 + 1 + 95 + 284 + 96 + 280 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +1.0 + 40 +1.0 + 40 +1.0 + 40 +2.0 + 40 +2.0 + 40 +2.0 + 40 +3.0 + 40 +3.0 + 40 +3.0 + 40 +4.0 + 40 +4.0 + 40 +4.0 + 40 +5.0 + 40 +5.0 + 40 +5.0 + 40 +6.0 + 40 +6.0 + 40 +6.0 + 40 +7.0 + 40 +7.0 + 40 +7.0 + 40 +8.0 + 40 +8.0 + 40 +8.0 + 40 +9.0 + 40 +9.0 + 40 +9.0 + 40 +10.0 + 40 +10.0 + 40 +10.0 + 40 +11.0 + 40 +11.0 + 40 +11.0 + 40 +12.0 + 40 +12.0 + 40 +12.0 + 40 +13.0 + 40 +13.0 + 40 +13.0 + 40 +14.0 + 40 +14.0 + 40 +14.0 + 40 +15.0 + 40 +15.0 + 40 +15.0 + 40 +16.0 + 40 +16.0 + 40 +16.0 + 40 +17.0 + 40 +17.0 + 40 +17.0 + 40 +18.0 + 40 +18.0 + 40 +18.0 + 40 +19.0 + 40 +19.0 + 40 +19.0 + 40 +20.0 + 40 +20.0 + 40 +20.0 + 40 +21.0 + 40 +21.0 + 40 +21.0 + 40 +22.0 + 40 +22.0 + 40 +22.0 + 40 +23.0 + 40 +23.0 + 40 +23.0 + 40 +24.0 + 40 +24.0 + 40 +24.0 + 40 +25.0 + 40 +25.0 + 40 +25.0 + 40 +26.0 + 40 +26.0 + 40 +26.0 + 40 +27.0 + 40 +27.0 + 40 +27.0 + 40 +28.0 + 40 +28.0 + 40 +28.0 + 40 +29.0 + 40 +29.0 + 40 +29.0 + 40 +30.0 + 40 +30.0 + 40 +30.0 + 40 +31.0 + 40 +31.0 + 40 +31.0 + 40 +32.0 + 40 +32.0 + 40 +32.0 + 40 +33.0 + 40 +33.0 + 40 +33.0 + 40 +34.0 + 40 +34.0 + 40 +34.0 + 40 +35.0 + 40 +35.0 + 40 +35.0 + 40 +36.0 + 40 +36.0 + 40 +36.0 + 40 +37.0 + 40 +37.0 + 40 +37.0 + 40 +38.0 + 40 +38.0 + 40 +38.0 + 40 +39.0 + 40 +39.0 + 40 +39.0 + 40 +40.0 + 40 +40.0 + 40 +40.0 + 40 +41.0 + 40 +41.0 + 40 +41.0 + 40 +42.0 + 40 +42.0 + 40 +42.0 + 40 +43.0 + 40 +43.0 + 40 +43.0 + 40 +44.0 + 40 +44.0 + 40 +44.0 + 40 +45.0 + 40 +45.0 + 40 +45.0 + 40 +46.0 + 40 +46.0 + 40 +46.0 + 40 +47.0 + 40 +47.0 + 40 +47.0 + 40 +48.0 + 40 +48.0 + 40 +48.0 + 40 +49.0 + 40 +49.0 + 40 +49.0 + 40 +50.0 + 40 +50.0 + 40 +50.0 + 40 +51.0 + 40 +51.0 + 40 +51.0 + 40 +52.0 + 40 +52.0 + 40 +52.0 + 40 +53.0 + 40 +53.0 + 40 +53.0 + 40 +54.0 + 40 +54.0 + 40 +54.0 + 40 +55.0 + 40 +55.0 + 40 +55.0 + 40 +56.0 + 40 +56.0 + 40 +56.0 + 40 +57.0 + 40 +57.0 + 40 +57.0 + 40 +58.0 + 40 +58.0 + 40 +58.0 + 40 +59.0 + 40 +59.0 + 40 +59.0 + 40 +60.0 + 40 +60.0 + 40 +60.0 + 40 +61.0 + 40 +61.0 + 40 +61.0 + 40 +62.0 + 40 +62.0 + 40 +62.0 + 40 +63.0 + 40 +63.0 + 40 +63.0 + 40 +64.0 + 40 +64.0 + 40 +64.0 + 40 +65.0 + 40 +65.0 + 40 +65.0 + 40 +66.0 + 40 +66.0 + 40 +66.0 + 40 +67.0 + 40 +67.0 + 40 +67.0 + 40 +68.0 + 40 +68.0 + 40 +68.0 + 40 +69.0 + 40 +69.0 + 40 +69.0 + 40 +70.0 + 40 +70.0 + 40 +70.0 + 40 +71.0 + 40 +71.0 + 40 +71.0 + 40 +72.0 + 40 +72.0 + 40 +72.0 + 40 +73.0 + 40 +73.0 + 40 +73.0 + 40 +74.0 + 40 +74.0 + 40 +74.0 + 40 +75.0 + 40 +75.0 + 40 +75.0 + 40 +76.0 + 40 +76.0 + 40 +76.0 + 40 +77.0 + 40 +77.0 + 40 +77.0 + 40 +78.0 + 40 +78.0 + 40 +78.0 + 40 +79.0 + 40 +79.0 + 40 +79.0 + 40 +80.0 + 40 +80.0 + 40 +80.0 + 40 +81.0 + 40 +81.0 + 40 +81.0 + 40 +82.0 + 40 +82.0 + 40 +82.0 + 40 +83.0 + 40 +83.0 + 40 +83.0 + 40 +84.0 + 40 +84.0 + 40 +84.0 + 40 +85.0 + 40 +85.0 + 40 +85.0 + 40 +86.0 + 40 +86.0 + 40 +86.0 + 40 +87.0 + 40 +87.0 + 40 +87.0 + 40 +88.0 + 40 +88.0 + 40 +88.0 + 40 +89.0 + 40 +89.0 + 40 +89.0 + 40 +90.0 + 40 +90.0 + 40 +90.0 + 40 +91.0 + 40 +91.0 + 40 +91.0 + 40 +92.0 + 40 +92.0 + 40 +92.0 + 40 +93.0 + 40 +93.0 + 40 +93.0 + 40 +93.0 + 10 +21.65402909338682 + 20 +-24.86040520332295 + 10 +21.66121542656927 + 20 +-25.55025152257897 + 10 +21.66480749668002 + 20 +-26.1826093693364 + 10 +21.66840066327126 + 20 +-26.74310936667181 + 10 +21.67199382986246 + 20 +-27.0233588170993 + 10 +21.67199382986246 + 20 +-27.28564462753164 + 10 +21.67558699645373 + 20 +-27.52996460500786 + 10 +21.67558699645373 + 20 +-27.65212459374601 + 10 +21.67558699645373 + 20 +-27.76709934578215 + 10 +21.67558699645373 + 20 +-27.87848093122711 + 10 +21.67558699645373 + 20 +-27.93237514065395 + 10 +21.67558699645373 + 20 +-27.98986251667207 + 10 +21.67558699645373 + 20 +-28.04016355950767 + 10 +21.67558699645373 + 20 +-28.06531462916574 + 10 +21.67558699645373 + 20 +-28.09405776893456 + 10 +21.67558699645373 + 20 +-28.11920883859261 + 10 +21.67558699645373 + 20 +-28.11920883859261 + 10 +21.67558699645373 + 20 +-28.13717357506822 + 10 +21.67558699645373 + 20 +-28.13717357506822 + 10 +21.67558699645373 + 20 +-28.13717357506822 + 10 +21.67558699645373 + 20 +-28.14795197836141 + 10 +21.67558699645373 + 20 +-28.14795197836141 + 10 +21.67558699645373 + 20 +-28.14795197836141 + 10 +21.67558699645373 + 20 +-28.14795197836141 + 10 +21.67558699645373 + 20 +-28.14795197836141 + 10 +21.67558699645373 + 20 +-28.14795197836141 + 10 +21.67558699645373 + 20 +-28.15154514495263 + 10 +21.67558699645373 + 20 +-28.15154514495263 + 10 +21.67558699645373 + 20 +-28.16591671483704 + 10 +21.67558699645373 + 20 +-28.17669621461068 + 10 +21.67558699645373 + 20 +-28.19106778449506 + 10 +21.67558699645373 + 20 +-28.20184618778825 + 10 +21.67558699645373 + 20 +-28.21621885415316 + 10 +21.67558699645373 + 20 +-28.22699725744633 + 10 +21.67558699645373 + 20 +-28.23777566073952 + 10 +21.67199382986246 + 20 +-28.24855516051318 + 10 +21.67199382986246 + 20 +-28.25574039721512 + 10 +21.66840066327126 + 20 +-28.27729830028195 + 10 +21.66840066327126 + 20 +-28.29526303675756 + 10 +21.66121542656927 + 20 +-28.31682093982444 + 10 +21.65402909338682 + 20 +-28.33837884289131 + 10 +21.6504370232761 + 20 +-28.35634357936689 + 10 +21.64325069009374 + 20 +-28.37790148243372 + 10 +21.63606435691127 + 20 +-28.39586621890933 + 10 +21.62887912020927 + 20 +-28.41742412197621 + 10 +21.62169278702681 + 20 +-28.43538885845181 + 10 +21.58935648066679 + 20 +-28.50724780435429 + 10 +21.54983384112433 + 20 +-28.5719204170743 + 10 +21.50671803499068 + 20 +-28.6258146265012 + 10 +21.41689435261254 + 20 +-28.73000987876369 + 10 +21.31629226694132 + 20 +-28.79468358796422 + 10 +21.22646858456318 + 20 +-28.83061306091546 + 10 +21.14023806877634 + 20 +-28.8665425338667 + 10 +21.06478595628267 + 20 +-28.87732093715989 + 10 +21.01807808003819 + 20 +-28.88450727034231 + 10 +20.96777703720254 + 20 +-28.88810043693353 + 10 +20.94262596754452 + 20 +-28.88810043693353 + 10 +20.94262596754452 + 20 +-28.88810043693353 + 10 +20.94262596754452 + 20 +-28.88810043693353 + 10 +20.48272915236084 + 20 +-28.88450727034231 + 10 +19.67790808106868 + 20 +-28.88450727034231 + 10 +19.27549754542257 + 20 +-28.88450727034231 + 10 +18.78685759047014 + 20 +-28.88091410375106 + 10 +18.22635759313468 + 20 +-28.87372886704917 + 10 +17.66585759579925 + 20 +-28.8665425338667 + 10 +17.03349865256137 + 20 +-28.85935729716476 + 10 +16.34365342978584 + 20 +-28.85217096398229 + 10 +15.99873027015784 + 20 +-28.84857779739107 + 10 +15.63943554064543 + 20 +-28.84498463079982 + 10 +15.26576924124864 + 20 +-28.8413925606891 + 10 +15.17235239227933 + 20 +-28.8413925606891 + 10 +15.07893554331 + 20 +-28.83779939409788 + 10 +14.98551979082117 + 20 +-28.83779939409788 + 10 +14.93881191457679 + 20 +-28.83779939409788 + 10 +14.89210294185184 + 20 +-28.83779939409788 + 10 +14.84180189901619 + 20 +-28.83779939409788 + 10 +14.84180189901619 + 20 +-28.83779939409788 + 10 +14.82383716254058 + 20 +-28.83779939409788 + 10 +14.82383716254058 + 20 +-28.83779939409788 + 10 +14.82383716254058 + 20 +-28.83779939409788 + 10 +14.82383716254058 + 20 +-28.83779939409788 + 10 +14.82024399594937 + 20 +-28.83779939409788 + 10 +14.82024399594937 + 20 +-28.83779939409788 + 10 +14.8130576627669 + 20 +-28.83779939409788 + 10 +14.8130576627669 + 20 +-28.83779939409788 + 10 +14.8130576627669 + 20 +-28.83779939409788 + 10 +14.77712818981571 + 20 +-28.83420622750668 + 10 +14.77712818981571 + 20 +-28.83420622750668 + 10 +14.77712818981571 + 20 +-28.83420622750668 + 10 +14.74119871686442 + 20 +-28.83061306091546 + 10 +14.74119871686442 + 20 +-28.83061306091546 + 10 +14.73042031357128 + 20 +-28.83061306091546 + 10 +14.72323398038881 + 20 +-28.82701989432422 + 10 +14.7160476472064 + 20 +-28.82701989432422 + 10 +14.68371134084638 + 20 +-28.82342672773299 + 10 +14.64418870130392 + 20 +-28.81264832443986 + 10 +14.60466606176144 + 20 +-28.80186882466617 + 10 +14.60466606176144 + 20 +-28.80186882466617 + 10 +14.55077185233459 + 20 +-28.78031092159929 + 10 +14.55077185233459 + 20 +-28.78031092159929 + 10 +14.55077185233459 + 20 +-28.78031092159929 + 10 +14.49687764290772 + 20 +-28.75515985194129 + 10 +14.49687764290772 + 20 +-28.75515985194129 + 10 +14.42861186359647 + 20 +-28.71563721239881 + 10 +14.36393815439593 + 20 +-28.66533616956316 + 10 +14.31004394496904 + 20 +-28.60425562695385 + 10 +14.20584869270654 + 20 +-28.48209563821572 + 10 +14.14117498350606 + 20 +-28.32400617652638 + 10 +14.13758291339534 + 20 +-28.15873038165457 + 10 +14.13398974680407 + 20 +-27.9575251138315 + 10 +14.13398974680407 + 20 +-27.75272777589779 + 10 +14.13039658021287 + 20 +-27.54433617489227 + 10 +14.12680341362165 + 20 +-27.1275540693618 + 10 +14.12321024703043 + 20 +-26.70358672712935 + 10 +14.1196181769197 + 20 +-26.27243305171449 + 10 +14.10883977362651 + 20 +-25.41012570088472 + 10 +14.10165344044405 + 20 +-24.51907521028617 + 10 +14.09087503715088 + 20 +-23.61365205332271 + 10 +14.08728187055969 + 20 +-23.16094047484097 + 10 +14.08368870396847 + 20 +-22.70463682624856 + 10 +14.08009663385774 + 20 +-22.24473891458441 + 10 +14.08009663385774 + 20 +-22.24473891458441 + 10 +14.08009663385774 + 20 +-22.20162310845068 + 10 +14.08009663385774 + 20 +-22.20162310845068 + 10 +14.08009663385774 + 20 +-22.20162310845068 + 10 +14.08009663385774 + 20 +-22.18006520538382 + 10 +14.08009663385774 + 20 +-22.18006520538382 + 10 +14.08009663385774 + 20 +-22.18006520538382 + 10 +14.08009663385774 + 20 +-22.18006520538382 + 10 +14.08009663385774 + 20 +-22.18006520538382 + 10 +14.08009663385774 + 20 +-22.18006520538382 + 10 +14.08009663385774 + 20 +-22.18006520538382 + 10 +14.08009663385774 + 20 +-22.17647203879265 + 10 +14.08009663385774 + 20 +-22.17647203879265 + 10 +14.08368980044896 + 20 +-22.12257782936576 + 10 +14.08368980044896 + 20 +-22.12257782936576 + 10 +14.08368980044896 + 20 +-22.12257782936576 + 10 +14.08368980044896 + 20 +-22.10101992629893 + 10 +14.08368980044896 + 20 +-22.10101992629893 + 10 +14.08368980044896 + 20 +-22.09383359311646 + 10 +14.08728296704016 + 20 +-22.09024152300577 + 10 +14.08728296704016 + 20 +-22.0830551898233 + 10 +14.09446930022263 + 20 +-22.03993938368959 + 10 +14.10524770351579 + 20 +-21.98963834085397 + 10 +14.11961927340018 + 20 +-21.93933729801837 + 10 +14.15195557976022 + 20 +-21.84592044904901 + 10 +14.20584978918706 + 20 +-21.75609676667093 + 10 +14.27411556849832 + 20 +-21.68064465417721 + 10 +14.41064712712083 + 20 +-21.52974042918984 + 10 +14.60825922835263 + 20 +-21.43991729505198 + 10 +14.8130576627669 + 20 +-21.43991729505198 + 10 +15.73285238961474 + 20 +-21.43991729505198 + 10 +16.6526471164626 + 20 +-21.44351046164318 + 10 +17.55806917694559 + 20 +-21.45428886493634 + 10 +18.46349233390896 + 20 +-21.46147464987853 + 10 +19.35454282450751 + 20 +-21.472253601412 + 10 +20.21685017533726 + 20 +-21.47943938635419 + 10 +20.43242701304472 + 20 +-21.47943938635419 + 10 +20.64800385075212 + 20 +-21.48303255294541 + 10 +20.85639435527714 + 20 +-21.48303255294541 + 10 +20.85639435527714 + 20 +-21.48303255294541 + 10 +20.92825330117961 + 20 +-21.48303255294541 + 10 +20.92825330117961 + 20 +-21.48303255294541 + 10 +20.92825330117961 + 20 +-21.48303255294541 + 10 +21.00729858026456 + 20 +-21.48662571953663 + 10 +21.00729858026456 + 20 +-21.48662571953663 + 10 +21.02885648333138 + 20 +-21.49021888612782 + 10 +21.04682121980702 + 20 +-21.49381150447882 + 10 +21.06837912287384 + 20 +-21.49740467107004 + 10 +21.0899370259407 + 20 +-21.50099783766129 + 10 +21.11149492900752 + 20 +-21.50818362260346 + 10 +21.12945966548316 + 20 +-21.5117767891947 + 10 +21.21209701467882 + 20 +-21.53692785885275 + 10 +21.29114229376377 + 20 +-21.57644940191472 + 10 +21.35940807307497 + 20 +-21.63034361134161 + 10 +21.49593963169749 + 20 +-21.7345388636041 + 10 +21.58935648066679 + 20 +-21.89262832529344 + 10 +21.62169278702681 + 20 +-22.05790412016523 + 10 +21.62528595361808 + 20 +-22.07946202323208 + 10 +21.62887912020927 + 20 +-22.10101992629893 + 10 +21.62887912020927 + 20 +-22.11898466277454 + 10 +21.62887912020927 + 20 +-22.14054256584136 + 10 +21.63247228680055 + 20 +-22.15850730231699 + 10 +21.63247228680055 + 20 +-22.18365727549457 + 10 +21.63247228680055 + 20 +-22.23395831833022 + 10 +21.63247228680055 + 20 +-22.28425936116587 + 10 +21.63247228680055 + 20 +-22.33456150048199 + 10 +21.63247228680055 + 20 +-22.43516358615327 + 10 +21.63247228680055 + 20 +-22.53576676830501 + 10 +21.63247228680055 + 20 +-22.63277568738512 + 10 +21.63247228680055 + 20 +-22.85553885827497 + 10 +21.63247228680055 + 20 +-23.07830093268436 + 10 +21.63606545339174 + 20 +-23.29387777039179 + 10 +21.63606545339174 + 20 +-23.29387777039179 + 10 +20.95340546731818 + 20 +-23.69269513944664 + 10 +20.95340546731818 + 20 +-23.69269513944664 + 10 +20.95340546731818 + 20 +-23.69269513944664 + 10 +20.9390338974338 + 20 +-22.26988779128146 + 10 +20.9390338974338 + 20 +-22.26988779128146 + 10 +20.9390338974338 + 20 +-22.21599358185457 + 10 +20.89591809130007 + 20 +-22.17287777572091 + 10 +20.84202388187322 + 20 +-22.17287777572091 + 10 +20.84202388187322 + 20 +-22.17287777572091 + 10 +20.84202388187322 + 20 +-22.17287777572091 + 10 +20.84202388187322 + 20 +-22.17287777572091 + 10 +20.5941107378058 + 20 +-22.16928460912969 + 10 +20.34619759373832 + 20 +-22.16569144253845 + 10 +20.09828335319035 + 20 +-22.16209937242772 + 10 +20.09828335319035 + 20 +-22.16209937242772 + 10 +19.35454282450751 + 20 +-22.15132096913456 + 10 +19.35454282450751 + 20 +-22.15132096913456 + 10 +19.35454282450751 + 20 +-22.15132096913456 + 10 +17.87065603021352 + 20 +-22.13694939925017 + 10 +17.87065603021352 + 20 +-22.13694939925017 + 10 +17.87065603021352 + 20 +-22.13694939925017 + 10 +16.38676923591955 + 20 +-22.13335623265895 + 10 +16.38676923591955 + 20 +-22.13335623265895 + 10 +16.38676923591955 + 20 +-22.13335623265895 + 10 +15.64302870723671 + 20 +-22.12976306606773 + 10 +15.64302870723671 + 20 +-22.12976306606773 + 10 +15.64302870723671 + 20 +-22.12976306606773 + 10 +14.89928817855379 + 20 +-22.12976306606773 + 10 +14.89928817855379 + 20 +-22.12976306606773 + 10 +14.89928817855379 + 20 +-22.12976306606773 + 10 +14.89928817855379 + 20 +-22.12976306606773 + 10 +14.89928817855379 + 20 +-22.12976306606773 + 10 +14.82383606606009 + 20 +-22.12976306606773 + 10 +14.7627566199313 + 20 +-22.19084360867701 + 10 +14.7627566199313 + 20 +-22.26988779128146 + 10 +14.7627566199313 + 20 +-22.26988779128146 + 10 +14.76634978652252 + 20 +-23.71784511262422 + 10 +14.76634978652252 + 20 +-23.71784511262422 + 10 +14.76634978652252 + 20 +-23.71784511262422 + 10 +14.76634978652252 + 20 +-24.44002773824021 + 10 +14.76634978652252 + 20 +-24.44002773824021 + 10 +14.76634978652252 + 20 +-24.44002773824021 + 10 +14.76994295311371 + 20 +-25.16221036385625 + 10 +14.76994295311371 + 20 +-25.16221036385625 + 10 +14.76994295311371 + 20 +-25.16221036385625 + 10 +14.78072135640691 + 20 +-26.61016768519903 + 10 +14.78072135640691 + 20 +-26.61016768519903 + 10 +14.78790768958935 + 20 +-27.09162240344957 + 10 +14.79149975970007 + 20 +-27.57667028829133 + 10 +14.79868609288254 + 20 +-28.05812500654179 + 10 +14.79868609288254 + 20 +-28.05812500654179 + 10 +14.79868609288254 + 20 +-28.05812500654179 + 10 +14.79868609288254 + 20 +-28.05812500654179 + 10 +14.79868609288254 + 20 +-28.11201921596868 + 10 +14.84180189901619 + 20 +-28.15513502210236 + 10 +14.89569610844309 + 20 +-28.15513502210236 + 10 +14.89569610844309 + 20 +-28.15513502210236 + 10 +19.35095075439679 + 20 +-28.20543606493798 + 10 +19.35095075439679 + 20 +-28.20543606493798 + 10 +19.35095075439679 + 20 +-28.20543606493798 + 10 +20.09469128307965 + 20 +-28.2126223981204 + 10 +20.09469128307965 + 20 +-28.2126223981204 + 10 +20.09469128307965 + 20 +-28.2126223981204 + 10 +20.46476441588523 + 20 +-28.21621556471167 + 10 +20.46476441588523 + 20 +-28.21621556471167 + 10 +20.58692440462333 + 20 +-28.21621556471167 + 10 +20.71267755995271 + 20 +-28.21621556471167 + 10 +20.83483754869076 + 20 +-28.21621556471167 + 10 +20.83483754869076 + 20 +-28.21621556471167 + 10 +20.83843071528203 + 20 +-28.21621556471167 + 10 +20.83843071528203 + 20 +-28.21621556471167 + 10 +20.83843071528203 + 20 +-28.21621556471167 + 10 +20.83843071528203 + 20 +-28.21621556471167 + 10 +20.83843071528203 + 20 +-28.21621556471167 + 10 +20.92466123106889 + 20 +-28.21621556471167 + 10 +20.99292701038017 + 20 +-28.14435661880919 + 10 +20.99292701038017 + 20 +-28.05812610302231 + 10 +20.99292701038017 + 20 +-28.05812610302231 + 10 +20.96059070402018 + 20 +-25.08316508477133 + 10 +20.96059070402018 + 20 +-25.08316508477133 + 10 +20.96059070402018 + 20 +-25.08316508477133 + 10 +21.63606435691127 + 20 +-24.68794088230768 + 10 +21.63606435691127 + 20 +-24.68794088230768 + 10 +21.65043592679563 + 20 +-24.74543154776726 + 10 +21.65043592679563 + 20 +-24.80291782730483 + 10 +21.65402909338682 + 20 +-24.86040520332295 + 97 + 0 + 97 + 0 + 92 + 1 + 93 + 1 + 72 + 4 + 94 + 3 + 73 + 0 + 74 + 1 + 95 + 215 + 96 + 211 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +1.0 + 40 +1.0 + 40 +1.0 + 40 +2.0 + 40 +2.0 + 40 +2.0 + 40 +3.0 + 40 +3.0 + 40 +3.0 + 40 +4.0 + 40 +4.0 + 40 +4.0 + 40 +5.0 + 40 +5.0 + 40 +5.0 + 40 +6.0 + 40 +6.0 + 40 +6.0 + 40 +7.0 + 40 +7.0 + 40 +7.0 + 40 +8.0 + 40 +8.0 + 40 +8.0 + 40 +9.0 + 40 +9.0 + 40 +9.0 + 40 +10.0 + 40 +10.0 + 40 +10.0 + 40 +11.0 + 40 +11.0 + 40 +11.0 + 40 +12.0 + 40 +12.0 + 40 +12.0 + 40 +13.0 + 40 +13.0 + 40 +13.0 + 40 +14.0 + 40 +14.0 + 40 +14.0 + 40 +15.0 + 40 +15.0 + 40 +15.0 + 40 +16.0 + 40 +16.0 + 40 +16.0 + 40 +17.0 + 40 +17.0 + 40 +17.0 + 40 +18.0 + 40 +18.0 + 40 +18.0 + 40 +19.0 + 40 +19.0 + 40 +19.0 + 40 +20.0 + 40 +20.0 + 40 +20.0 + 40 +21.0 + 40 +21.0 + 40 +21.0 + 40 +22.0 + 40 +22.0 + 40 +22.0 + 40 +23.0 + 40 +23.0 + 40 +23.0 + 40 +24.0 + 40 +24.0 + 40 +24.0 + 40 +25.0 + 40 +25.0 + 40 +25.0 + 40 +26.0 + 40 +26.0 + 40 +26.0 + 40 +27.0 + 40 +27.0 + 40 +27.0 + 40 +28.0 + 40 +28.0 + 40 +28.0 + 40 +29.0 + 40 +29.0 + 40 +29.0 + 40 +30.0 + 40 +30.0 + 40 +30.0 + 40 +31.0 + 40 +31.0 + 40 +31.0 + 40 +32.0 + 40 +32.0 + 40 +32.0 + 40 +33.0 + 40 +33.0 + 40 +33.0 + 40 +34.0 + 40 +34.0 + 40 +34.0 + 40 +35.0 + 40 +35.0 + 40 +35.0 + 40 +36.0 + 40 +36.0 + 40 +36.0 + 40 +37.0 + 40 +37.0 + 40 +37.0 + 40 +38.0 + 40 +38.0 + 40 +38.0 + 40 +39.0 + 40 +39.0 + 40 +39.0 + 40 +40.0 + 40 +40.0 + 40 +40.0 + 40 +41.0 + 40 +41.0 + 40 +41.0 + 40 +42.0 + 40 +42.0 + 40 +42.0 + 40 +43.0 + 40 +43.0 + 40 +43.0 + 40 +44.0 + 40 +44.0 + 40 +44.0 + 40 +45.0 + 40 +45.0 + 40 +45.0 + 40 +46.0 + 40 +46.0 + 40 +46.0 + 40 +47.0 + 40 +47.0 + 40 +47.0 + 40 +48.0 + 40 +48.0 + 40 +48.0 + 40 +49.0 + 40 +49.0 + 40 +49.0 + 40 +50.0 + 40 +50.0 + 40 +50.0 + 40 +51.0 + 40 +51.0 + 40 +51.0 + 40 +52.0 + 40 +52.0 + 40 +52.0 + 40 +53.0 + 40 +53.0 + 40 +53.0 + 40 +54.0 + 40 +54.0 + 40 +54.0 + 40 +55.0 + 40 +55.0 + 40 +55.0 + 40 +56.0 + 40 +56.0 + 40 +56.0 + 40 +57.0 + 40 +57.0 + 40 +57.0 + 40 +58.0 + 40 +58.0 + 40 +58.0 + 40 +59.0 + 40 +59.0 + 40 +59.0 + 40 +60.0 + 40 +60.0 + 40 +60.0 + 40 +61.0 + 40 +61.0 + 40 +61.0 + 40 +62.0 + 40 +62.0 + 40 +62.0 + 40 +63.0 + 40 +63.0 + 40 +63.0 + 40 +64.0 + 40 +64.0 + 40 +64.0 + 40 +65.0 + 40 +65.0 + 40 +65.0 + 40 +66.0 + 40 +66.0 + 40 +66.0 + 40 +67.0 + 40 +67.0 + 40 +67.0 + 40 +68.0 + 40 +68.0 + 40 +68.0 + 40 +69.0 + 40 +69.0 + 40 +69.0 + 40 +70.0 + 40 +70.0 + 40 +70.0 + 40 +70.0 + 10 +17.38920021548251 + 20 +-25.79816466664644 + 10 +17.27063339333563 + 20 +-25.64366837154827 + 10 +17.15206547470823 + 20 +-25.48917097996966 + 10 +17.03349865256137 + 20 +-25.33826785146274 + 10 +17.03349865256137 + 20 +-25.33826785146274 + 10 +16.62390288021334 + 20 +-24.8101041604873 + 10 +16.62390288021334 + 20 +-24.8101041604873 + 10 +16.62390288021334 + 20 +-24.8101041604873 + 10 +16.51970762795084 + 20 +-24.68075783856673 + 10 +16.51970762795084 + 20 +-24.68075783856673 + 10 +16.50892922465765 + 20 +-24.66997943527359 + 10 +16.50533605806643 + 20 +-24.6591999354999 + 10 +16.49096448818204 + 20 +-24.64842153220674 + 10 +16.48018608488888 + 20 +-24.63404996232233 + 10 +16.46940658511519 + 20 +-24.62327046254866 + 10 +16.45503501523076 + 20 +-24.60889889266425 + 10 +16.45503501523076 + 20 +-24.60889889266425 + 10 +16.41551237568829 + 20 +-24.57296941971302 + 10 +16.41551237568829 + 20 +-24.57296941971302 + 10 +16.40114080580391 + 20 +-24.56219101641985 + 10 +16.38676923591955 + 20 +-24.55141151664619 + 10 +16.37239656955464 + 20 +-24.540633113353 + 10 +16.25382974740776 + 20 +-24.45799576415735 + 10 +16.10651868901156 + 20 +-24.41847312461487 + 10 +15.96639396379783 + 20 +-24.4364378610905 + 10 +15.89453501789535 + 20 +-24.44362419427297 + 10 +15.82267607199287 + 20 +-24.46518100085932 + 10 +15.75800345927284 + 20 +-24.49751840369981 + 10 +15.74003872279721 + 20 +-24.50829680699301 + 10 +15.7220739863216 + 20 +-24.51548314017544 + 10 +15.70410924984599 + 20 +-24.52626154346864 + 10 +15.69333084655283 + 20 +-24.53344787665105 + 10 +15.68973767996158 + 20 +-24.53344787665105 + 10 +15.68614451337036 + 20 +-24.540633113353 + 10 +15.68614451337036 + 20 +-24.540633113353 + 10 +15.6681797768947 + 20 +-24.55141151664619 + 10 +15.6681797768947 + 20 +-24.55141151664619 + 10 +15.6681797768947 + 20 +-24.55141151664619 + 10 +15.66099344371226 + 20 +-24.55859784982865 + 10 +15.66099344371226 + 20 +-24.55859784982865 + 10 +15.66099344371226 + 20 +-24.55859784982865 + 10 +15.65380711052985 + 20 +-24.56578418301109 + 10 +15.65380711052985 + 20 +-24.56578418301109 + 10 +15.65380711052985 + 20 +-24.56578418301109 + 10 +15.6502139439386 + 20 +-24.56937734960229 + 10 +15.6502139439386 + 20 +-24.56937734960229 + 10 +15.6502139439386 + 20 +-24.56937734960229 + 10 +15.64662077734743 + 20 +-24.57297051619354 + 10 +15.64662077734743 + 20 +-24.57297051619354 + 10 +15.64662077734743 + 20 +-24.57297051619354 + 10 +15.62506287428055 + 20 +-24.59093525266912 + 10 +15.62506287428055 + 20 +-24.59093525266912 + 10 +15.61069130439614 + 20 +-24.60171365596231 + 10 +15.59991180462248 + 20 +-24.61608632232724 + 10 +15.58554023473807 + 20 +-24.6304578922116 + 10 +15.48493814906682 + 20 +-24.73824631106532 + 10 +15.42745077304873 + 20 +-24.87837103627905 + 10 +15.42385760645751 + 20 +-25.02568209467525 + 10 +15.42385760645751 + 20 +-25.0975410405777 + 10 +15.4346360097507 + 20 +-25.17299315307145 + 10 +15.45619391281753 + 20 +-25.24125893238266 + 10 +15.45978707940874 + 20 +-25.25922366885831 + 10 +15.47056548270189 + 20 +-25.27718840533394 + 10 +15.47775181588435 + 20 +-25.29155997521831 + 10 +15.47775181588435 + 20 +-25.29155997521831 + 10 +15.48853021917754 + 20 +-25.31671104487638 + 10 +15.48853021917754 + 20 +-25.31671104487638 + 10 +15.48853021917754 + 20 +-25.31671104487638 + 10 +15.50290178906195 + 20 +-25.34186211453445 + 10 +15.50290178906195 + 20 +-25.34186211453445 + 10 +15.50290178906195 + 20 +-25.34186211453445 + 10 +15.51727335894637 + 20 +-25.36701318419255 + 10 +15.51727335894637 + 20 +-25.36701318419255 + 10 +15.52445969212878 + 20 +-25.37779158748572 + 10 +15.52445969212878 + 20 +-25.37779158748572 + 10 +15.53164492883073 + 20 +-25.38497792066816 + 10 +15.53164492883073 + 20 +-25.38497792066816 + 10 +15.54242333212392 + 20 +-25.40294265714374 + 10 +15.54242333212392 + 20 +-25.40294265714374 + 10 +15.54242333212392 + 20 +-25.40294265714374 + 10 +15.54601649871509 + 20 +-25.40653582373501 + 10 +15.54601649871509 + 20 +-25.40653582373501 + 10 +15.54601649871509 + 20 +-25.40653582373501 + 10 +15.54601649871509 + 20 +-25.4101289903262 + 10 +15.54601649871509 + 20 +-25.4101289903262 + 10 +15.54601649871509 + 20 +-25.4101289903262 + 10 +15.55320283189755 + 20 +-25.41731532350867 + 10 +15.55320283189755 + 20 +-25.41731532350867 + 10 +15.55320283189755 + 20 +-25.41731532350867 + 10 +15.56038916508002 + 20 +-25.42450165669111 + 10 +15.56038916508002 + 20 +-25.42450165669111 + 10 +15.56038916508002 + 20 +-25.42450165669111 + 10 +15.76877966960501 + 20 +-25.68678637064295 + 10 +15.76877966960501 + 20 +-25.68678637064295 + 10 +15.76877966960501 + 20 +-25.68678637064295 + 10 +16.18556177513549 + 20 +-26.20776372843594 + 10 +16.18556177513549 + 20 +-26.20776372843594 + 10 +16.18556177513549 + 20 +-26.20776372843594 + 10 +16.60234388066599 + 20 +-26.72874108622894 + 10 +16.60234388066599 + 20 +-26.72874108622894 + 10 +16.60234388066599 + 20 +-26.72874108622894 + 10 +16.70653913292848 + 20 +-26.85808740814948 + 10 +16.70653913292848 + 20 +-26.85808740814948 + 10 +16.70653913292848 + 20 +-26.85808740814948 + 10 +16.76043334235533 + 20 +-26.92276002086952 + 10 +16.76043334235533 + 20 +-26.92276002086952 + 10 +16.78199124542216 + 20 +-26.94791109052757 + 10 +16.80714121859976 + 20 +-26.97665423029639 + 10 +16.83588545484905 + 20 +-27.00180529995441 + 10 +16.89337283086711 + 20 +-27.05210634279006 + 10 +16.95804544358715 + 20 +-27.09162898233254 + 10 +17.02631122289843 + 20 +-27.11678005199059 + 10 +17.06224069584965 + 20 +-27.13115162187501 + 10 +17.09817016880091 + 20 +-27.13833795505747 + 10 +17.13409964175212 + 20 +-27.14552319175937 + 10 +17.15206437822776 + 20 +-27.14911635835066 + 10 +17.17002911470336 + 20 +-27.15270952494183 + 10 +17.19158701777024 + 20 +-27.15270952494183 + 10 +17.19158701777024 + 20 +-27.15270952494183 + 10 +17.2059585876546 + 20 +-27.15270952494183 + 10 +17.2059585876546 + 20 +-27.15270952494183 + 10 +17.21314492083707 + 20 +-27.15270952494183 + 10 +17.21673699094779 + 20 +-27.15270952494183 + 10 +17.22033015753901 + 20 +-27.15270952494183 + 10 +17.22033015753901 + 20 +-27.15270952494183 + 10 +17.24188806060584 + 20 +-27.15270952494183 + 10 +17.24188806060584 + 20 +-27.15270952494183 + 10 +17.24188806060584 + 20 +-27.15270952494183 + 10 +17.24548122719706 + 20 +-27.15270952494183 + 10 +17.24548122719706 + 20 +-27.15270952494183 + 10 +17.34967647945955 + 20 +-27.15270952494183 + 10 +17.45746489831329 + 20 +-27.12396638517306 + 10 +17.54728858069138 + 20 +-27.07366424585689 + 10 +17.54728858069138 + 20 +-27.07366424585689 + 10 +17.75208701510564 + 20 +-26.95509742371003 + 10 +17.75208701510564 + 20 +-26.95509742371003 + 10 +17.75208701510564 + 20 +-26.95509742371003 + 10 +18.15808962086246 + 20 +-26.71796268293575 + 10 +18.15808962086246 + 20 +-26.71796268293575 + 10 +18.15808962086246 + 20 +-26.71796268293575 + 10 +18.9736890954478 + 20 +-26.24369320138718 + 10 +18.9736890954478 + 20 +-26.24369320138718 + 10 +18.9736890954478 + 20 +-26.24369320138718 + 10 +20.60488694813797 + 20 +-25.29515533477052 + 10 +20.60488694813797 + 20 +-25.29515533477052 + 10 +20.60488694813797 + 20 +-25.29515533477052 + 10 +20.70908220040042 + 20 +-25.23407479216121 + 10 +20.70908220040042 + 20 +-25.23407479216121 + 10 +20.70908220040042 + 20 +-25.23407479216121 + 10 +20.68033906063167 + 20 +-27.90363529032669 + 10 +20.68033906063167 + 20 +-27.90363529032669 + 10 +20.60848011472919 + 20 +-27.90363529032669 + 10 +20.53662116882672 + 20 +-27.90363529032669 + 10 +20.46476222292424 + 20 +-27.90363529032669 + 10 +20.46476222292424 + 20 +-27.90363529032669 + 10 +20.09468909011866 + 20 +-27.90722845691789 + 10 +20.09468909011866 + 20 +-27.90722845691789 + 10 +20.09468909011866 + 20 +-27.90722845691789 + 10 +19.35094856143577 + 20 +-27.91441479010033 + 10 +19.35094856143577 + 20 +-27.91441479010033 + 10 +19.35094856143577 + 20 +-27.91441479010033 + 10 +17.8670617671418 + 20 +-27.93237952657596 + 10 +17.8670617671418 + 20 +-27.93237952657596 + 10 +17.8670617671418 + 20 +-27.93237952657596 + 10 +14.99629709763389 + 20 +-27.96471583293598 + 10 +14.99629709763389 + 20 +-27.96471583293598 + 10 +15.00348343081628 + 20 +-27.51559742104544 + 10 +15.00707550092703 + 20 +-27.06647900915499 + 10 +15.01426183410947 + 20 +-26.61736059726445 + 10 +15.01426183410947 + 20 +-26.61736059726445 + 10 +15.02504023740261 + 20 +-25.16940327592172 + 10 +15.02504023740261 + 20 +-25.16940327592172 + 10 +15.02504023740261 + 20 +-25.16940327592172 + 10 +15.02863340399388 + 20 +-24.44722065030568 + 10 +15.02863340399388 + 20 +-24.44722065030568 + 10 +15.02863340399388 + 20 +-24.44722065030568 + 10 +15.02863340399388 + 20 +-23.72503802468966 + 10 +15.02863340399388 + 20 +-23.72503802468966 + 10 +15.02863340399388 + 20 +-23.72503802468966 + 10 +15.03222657058508 + 20 +-22.41720542856061 + 10 +15.03222657058508 + 20 +-22.41720542856061 + 10 +15.03222657058508 + 20 +-22.41720542856061 + 10 +15.63584127757374 + 20 +-22.41720542856061 + 10 +15.63584127757374 + 20 +-22.41720542856061 + 10 +15.63584127757374 + 20 +-22.41720542856061 + 10 +16.37958180625658 + 20 +-22.41361226196942 + 10 +16.37958180625658 + 20 +-22.41361226196942 + 10 +16.37958180625658 + 20 +-22.41361226196942 + 10 +17.8634686005506 + 20 +-22.41001909537815 + 10 +17.8634686005506 + 20 +-22.41001909537815 + 10 +17.8634686005506 + 20 +-22.41001909537815 + 10 +19.34735539484457 + 20 +-22.39564752549379 + 10 +19.34735539484457 + 20 +-22.39564752549379 + 10 +19.34735539484457 + 20 +-22.39564752549379 + 10 +20.09109592352744 + 20 +-22.3848691222006 + 10 +20.09109592352744 + 20 +-22.3848691222006 + 10 +20.3066727612349 + 20 +-22.38127595560938 + 10 +20.51865643235106 + 20 +-22.37768278901816 + 10 +20.73423327005851 + 20 +-22.37409071890743 + 10 +20.73423327005851 + 20 +-22.37409071890743 + 10 +20.71986170017415 + 20 +-23.83282754002385 + 10 +20.71986170017415 + 20 +-23.83282754002385 + 10 +20.71986170017415 + 20 +-23.83282754002385 + 10 +20.54740066860035 + 20 +-23.93342962569515 + 10 +20.54740066860035 + 20 +-23.93342962569515 + 10 +20.54740066860035 + 20 +-23.93342962569515 + 10 +18.92338805261213 + 20 +-24.89274699208545 + 10 +18.92338805261213 + 20 +-24.89274699208545 + 10 +18.92338805261213 + 20 +-24.89274699208545 + 10 +17.38920021548251 + 20 +-25.79816466664644 + 10 +17.38920021548251 + 20 +-25.79816466664644 + 97 + 0 + 97 + 0 + 92 + 1 + 93 + 1 + 72 + 4 + 94 + 3 + 73 + 0 + 74 + 1 + 95 + 218 + 96 + 214 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +1.0 + 40 +1.0 + 40 +1.0 + 40 +2.0 + 40 +2.0 + 40 +2.0 + 40 +3.0 + 40 +3.0 + 40 +3.0 + 40 +4.0 + 40 +4.0 + 40 +4.0 + 40 +5.0 + 40 +5.0 + 40 +5.0 + 40 +6.0 + 40 +6.0 + 40 +6.0 + 40 +7.0 + 40 +7.0 + 40 +7.0 + 40 +8.0 + 40 +8.0 + 40 +8.0 + 40 +9.0 + 40 +9.0 + 40 +9.0 + 40 +10.0 + 40 +10.0 + 40 +10.0 + 40 +11.0 + 40 +11.0 + 40 +11.0 + 40 +12.0 + 40 +12.0 + 40 +12.0 + 40 +13.0 + 40 +13.0 + 40 +13.0 + 40 +14.0 + 40 +14.0 + 40 +14.0 + 40 +15.0 + 40 +15.0 + 40 +15.0 + 40 +16.0 + 40 +16.0 + 40 +16.0 + 40 +17.0 + 40 +17.0 + 40 +17.0 + 40 +18.0 + 40 +18.0 + 40 +18.0 + 40 +19.0 + 40 +19.0 + 40 +19.0 + 40 +20.0 + 40 +20.0 + 40 +20.0 + 40 +21.0 + 40 +21.0 + 40 +21.0 + 40 +22.0 + 40 +22.0 + 40 +22.0 + 40 +23.0 + 40 +23.0 + 40 +23.0 + 40 +24.0 + 40 +24.0 + 40 +24.0 + 40 +25.0 + 40 +25.0 + 40 +25.0 + 40 +26.0 + 40 +26.0 + 40 +26.0 + 40 +27.0 + 40 +27.0 + 40 +27.0 + 40 +28.0 + 40 +28.0 + 40 +28.0 + 40 +29.0 + 40 +29.0 + 40 +29.0 + 40 +30.0 + 40 +30.0 + 40 +30.0 + 40 +31.0 + 40 +31.0 + 40 +31.0 + 40 +32.0 + 40 +32.0 + 40 +32.0 + 40 +33.0 + 40 +33.0 + 40 +33.0 + 40 +34.0 + 40 +34.0 + 40 +34.0 + 40 +35.0 + 40 +35.0 + 40 +35.0 + 40 +36.0 + 40 +36.0 + 40 +36.0 + 40 +37.0 + 40 +37.0 + 40 +37.0 + 40 +38.0 + 40 +38.0 + 40 +38.0 + 40 +39.0 + 40 +39.0 + 40 +39.0 + 40 +40.0 + 40 +40.0 + 40 +40.0 + 40 +41.0 + 40 +41.0 + 40 +41.0 + 40 +42.0 + 40 +42.0 + 40 +42.0 + 40 +43.0 + 40 +43.0 + 40 +43.0 + 40 +44.0 + 40 +44.0 + 40 +44.0 + 40 +45.0 + 40 +45.0 + 40 +45.0 + 40 +46.0 + 40 +46.0 + 40 +46.0 + 40 +47.0 + 40 +47.0 + 40 +47.0 + 40 +48.0 + 40 +48.0 + 40 +48.0 + 40 +49.0 + 40 +49.0 + 40 +49.0 + 40 +50.0 + 40 +50.0 + 40 +50.0 + 40 +51.0 + 40 +51.0 + 40 +51.0 + 40 +52.0 + 40 +52.0 + 40 +52.0 + 40 +53.0 + 40 +53.0 + 40 +53.0 + 40 +54.0 + 40 +54.0 + 40 +54.0 + 40 +55.0 + 40 +55.0 + 40 +55.0 + 40 +56.0 + 40 +56.0 + 40 +56.0 + 40 +57.0 + 40 +57.0 + 40 +57.0 + 40 +58.0 + 40 +58.0 + 40 +58.0 + 40 +59.0 + 40 +59.0 + 40 +59.0 + 40 +60.0 + 40 +60.0 + 40 +60.0 + 40 +61.0 + 40 +61.0 + 40 +61.0 + 40 +62.0 + 40 +62.0 + 40 +62.0 + 40 +63.0 + 40 +63.0 + 40 +63.0 + 40 +64.0 + 40 +64.0 + 40 +64.0 + 40 +65.0 + 40 +65.0 + 40 +65.0 + 40 +66.0 + 40 +66.0 + 40 +66.0 + 40 +67.0 + 40 +67.0 + 40 +67.0 + 40 +68.0 + 40 +68.0 + 40 +68.0 + 40 +69.0 + 40 +69.0 + 40 +69.0 + 40 +70.0 + 40 +70.0 + 40 +70.0 + 40 +71.0 + 40 +71.0 + 40 +71.0 + 40 +71.0 + 10 +23.52954802003377 + 20 +-23.12501209837023 + 10 +23.5187696167406 + 20 +-23.17890630779713 + 10 +23.49721061719331 + 20 +-23.22920735063273 + 10 +23.46128114424202 + 20 +-23.27232315676643 + 10 +23.44331640776636 + 20 +-23.29388105983331 + 10 +23.42535167129083 + 20 +-23.31184579630892 + 10 +23.40379486470445 + 20 +-23.32621736619333 + 10 +23.39301646141126 + 20 +-23.33340369937574 + 10 +23.38223805811809 + 20 +-23.34058893607769 + 10 +23.36786539175316 + 20 +-23.34777526926016 + 10 +23.36786539175316 + 20 +-23.34777526926016 + 10 +23.31756325243704 + 20 +-23.37651840902893 + 10 +23.31756325243704 + 20 +-23.37651840902893 + 10 +23.31756325243704 + 20 +-23.37651840902893 + 10 +22.90796638360851 + 20 +-23.61005998321199 + 10 +22.90796638360851 + 20 +-23.61005998321199 + 10 +22.90796638360851 + 20 +-23.61005998321199 + 10 +22.09236690902316 + 20 +-24.08073629816936 + 10 +22.09236690902316 + 20 +-24.08073629816936 + 10 +22.09236690902316 + 20 +-24.08073629816936 + 10 +20.46117015281349 + 20 +-25.02927416478597 + 10 +20.46117015281349 + 20 +-25.02927416478597 + 10 +20.46117015281349 + 20 +-25.02927416478597 + 10 +18.82997230012329 + 20 +-25.97421886481142 + 10 +18.82997230012329 + 20 +-25.97421886481142 + 10 +18.82997230012329 + 20 +-25.97421886481142 + 10 +18.01437282553803 + 20 +-26.44489517976869 + 10 +18.01437282553803 + 20 +-26.44489517976869 + 10 +18.01437282553803 + 20 +-26.44489517976869 + 10 +17.60477705318997 + 20 +-26.68202992054299 + 10 +17.60477705318997 + 20 +-26.68202992054299 + 10 +17.60477705318997 + 20 +-26.68202992054299 + 10 +17.40357178536692 + 20 +-26.79700467257918 + 10 +17.40357178536692 + 20 +-26.79700467257918 + 10 +17.35686390912249 + 20 +-26.82215574223722 + 10 +17.30656176980637 + 20 +-26.83652731212164 + 10 +17.25626072697072 + 20 +-26.83652731212164 + 10 +17.25626072697072 + 20 +-26.83652731212164 + 10 +17.23829599049511 + 20 +-26.83652731212164 + 10 +17.23829599049511 + 20 +-26.83652731212164 + 10 +17.23470282390389 + 20 +-26.83652731212164 + 10 +17.2311096573127 + 20 +-26.83652731212164 + 10 +17.2311096573127 + 20 +-26.83652731212164 + 10 +17.2311096573127 + 20 +-26.83652731212164 + 10 +17.22751649072143 + 20 +-26.83652731212164 + 10 +17.22751649072143 + 20 +-26.83652731212164 + 10 +17.22033015753901 + 20 +-26.83652731212164 + 10 +17.20955175424584 + 20 +-26.83293414553042 + 10 +17.2023654210634 + 20 +-26.83293414553042 + 10 +17.18440068458777 + 20 +-26.82934097893922 + 10 +17.16643594811214 + 20 +-26.82574781234795 + 10 +17.14847121163653 + 20 +-26.81856257564603 + 10 +17.11254173868529 + 20 +-26.80778417235286 + 10 +17.08379859891647 + 20 +-26.78622626928604 + 10 +17.05505436266718 + 20 +-26.76466836621916 + 10 +17.04068279278282 + 20 +-26.75029679633475 + 10 +17.02990329300916 + 20 +-26.73951729656109 + 10 +17.01553172312475 + 20 +-26.72514572667673 + 10 +17.01553172312475 + 20 +-26.72514572667673 + 10 +16.96163751369785 + 20 +-26.66047311395667 + 10 +16.96163751369785 + 20 +-26.66047311395667 + 10 +16.96163751369785 + 20 +-26.66047311395667 + 10 +16.85744226143536 + 20 +-26.5311267920361 + 10 +16.85744226143536 + 20 +-26.5311267920361 + 10 +16.85744226143536 + 20 +-26.5311267920361 + 10 +16.43706698931368 + 20 +-26.01374260083435 + 10 +16.43706698931368 + 20 +-26.01374260083435 + 10 +16.43706698931368 + 20 +-26.01374260083435 + 10 +16.00950867345103 + 20 +-25.49276414656086 + 10 +16.00950867345103 + 20 +-25.49276414656086 + 10 +16.00950867345103 + 20 +-25.49276414656086 + 10 +15.7975250023348 + 20 +-25.23407150271974 + 10 +15.7975250023348 + 20 +-25.23407150271974 + 10 +15.7975250023348 + 20 +-25.23407150271974 + 10 +15.79033866915233 + 20 +-25.22688516953728 + 10 +15.79033866915233 + 20 +-25.22688516953728 + 10 +15.79033866915233 + 20 +-25.22688516953728 + 10 +15.78674550256111 + 20 +-25.22329200294608 + 10 +15.78674550256111 + 20 +-25.22329200294608 + 10 +15.78674550256111 + 20 +-25.22329200294608 + 10 +15.77596709926795 + 20 +-25.20532726647045 + 10 +15.77596709926795 + 20 +-25.20532726647045 + 10 +15.77237393267675 + 20 +-25.20173409987923 + 10 +15.76518869597481 + 20 +-25.19454886317726 + 10 +15.76518869597481 + 20 +-25.19095569658609 + 10 +15.76518869597481 + 20 +-25.19095569658609 + 10 +15.76159552938356 + 20 +-25.1801772932929 + 10 +15.76159552938356 + 20 +-25.1801772932929 + 10 +15.76159552938356 + 20 +-25.1801772932929 + 10 +15.7544091962011 + 20 +-25.16939888999971 + 10 +15.7544091962011 + 20 +-25.16939888999971 + 10 +15.7544091962011 + 20 +-25.16939888999971 + 10 +15.74722286301865 + 20 +-25.15502732011529 + 10 +15.74722286301865 + 20 +-25.15502732011529 + 10 +15.74362969642746 + 20 +-25.14784098693288 + 10 +15.74003652983624 + 20 +-25.13706258363971 + 10 +15.73644445972546 + 20 +-25.12987625045725 + 10 +15.7220728898411 + 20 +-25.09394677750601 + 10 +15.71488655665864 + 20 +-25.05801730455475 + 10 +15.71488655665864 + 20 +-25.01849466501229 + 10 +15.71488655665864 + 20 +-24.94304255251862 + 10 +15.74362969642746 + 20 +-24.86399836991414 + 10 +15.79393183574358 + 20 +-24.8065109938961 + 10 +15.80111816892605 + 20 +-24.79932466071366 + 10 +15.80830340562794 + 20 +-24.79213942401169 + 10 +15.81548973881041 + 20 +-24.78495309082922 + 10 +15.81548973881041 + 20 +-24.78495309082922 + 10 +15.8262681421036 + 20 +-24.77417468753609 + 10 +15.8262681421036 + 20 +-24.77417468753609 + 10 +15.8262681421036 + 20 +-24.77417468753609 + 10 +15.82986130869482 + 20 +-24.77058152094484 + 10 +15.82986130869482 + 20 +-24.77058152094484 + 10 +15.82986130869482 + 20 +-24.77058152094484 + 10 +15.82986130869482 + 20 +-24.77058152094484 + 10 +15.82986130869482 + 20 +-24.77058152094484 + 10 +15.82986130869482 + 20 +-24.77058152094484 + 10 +15.82986130869482 + 20 +-24.77058152094484 + 10 +15.82986130869482 + 20 +-24.77058152094484 + 10 +15.82986130869482 + 20 +-24.77058152094484 + 10 +15.83345447528604 + 20 +-24.76698835435364 + 10 +15.83345447528604 + 20 +-24.76698835435364 + 10 +15.83345447528604 + 20 +-24.76698835435364 + 10 +15.85141921176169 + 20 +-24.75620995106045 + 10 +15.85141921176169 + 20 +-24.75620995106045 + 10 +15.85501237835287 + 20 +-24.75261678446923 + 10 +15.86219761505484 + 20 +-24.74543154776726 + 10 +15.86579078164606 + 20 +-24.74543154776726 + 10 +15.87297711482852 + 20 +-24.74183838117604 + 10 +15.88016235153044 + 20 +-24.73824521458482 + 10 +15.88734868471288 + 20 +-24.7346531444741 + 10 +15.92327815766412 + 20 +-24.71668840799849 + 10 +15.95920763061536 + 20 +-24.7059100047053 + 10 +15.99873027015784 + 20 +-24.69872367152288 + 10 +16.07777554924279 + 20 +-24.68794526822972 + 10 +16.16041289843841 + 20 +-24.7059100047053 + 10 +16.22508551115849 + 20 +-24.75261788094973 + 10 +16.23227184434091 + 20 +-24.7598042141322 + 10 +16.24305024763407 + 20 +-24.76339628424292 + 10 +16.25023658081649 + 20 +-24.77058261742534 + 10 +16.25023658081649 + 20 +-24.77058261742534 + 10 +16.27179448388337 + 20 +-24.78854735390097 + 10 +16.27179448388337 + 20 +-24.78854735390097 + 10 +16.27179448388337 + 20 +-24.78854735390097 + 10 +16.29335238695019 + 20 +-24.81010525696779 + 10 +16.29335238695019 + 20 +-24.81010525696779 + 10 +16.30053872013266 + 20 +-24.81729159015026 + 10 +16.3113171234258 + 20 +-24.83166316003467 + 10 +16.31850345660827 + 20 +-24.83884839673659 + 10 +16.31850345660827 + 20 +-24.83884839673659 + 10 +16.42629187546203 + 20 +-24.96819471865718 + 10 +16.42629187546203 + 20 +-24.96819471865718 + 10 +16.42629187546203 + 20 +-24.96819471865718 + 10 +16.85744555087684 + 20 +-25.47839367315694 + 10 +16.85744555087684 + 20 +-25.47839367315694 + 10 +17.0011634426818 + 20 +-25.64726263462002 + 10 +17.14488133448678 + 20 +-25.81972366619372 + 10 +17.28859922629176 + 20 +-25.9885926276568 + 10 +17.28859922629176 + 20 +-25.9885926276568 + 10 +17.28859922629176 + 20 +-25.9885926276568 + 10 +17.28859922629176 + 20 +-25.9885926276568 + 10 +17.28859922629176 + 20 +-25.9885926276568 + 10 +17.28859922629176 + 20 +-25.9885926276568 + 10 +17.28859922629176 + 20 +-25.9885926276568 + 10 +17.31734236606058 + 20 +-26.02452210060801 + 10 +17.37123657548742 + 20 +-26.03530050390117 + 10 +17.41435238162108 + 20 +-26.01015053072362 + 10 +17.41435238162108 + 20 +-26.01015053072362 + 10 +19.05273656749371 + 20 +-25.07598423399137 + 10 +19.05273656749371 + 20 +-25.07598423399137 + 10 +19.05273656749371 + 20 +-25.07598423399137 + 10 +20.69112075336635 + 20 +-24.14181793725912 + 10 +20.69112075336635 + 20 +-24.14181793725912 + 10 +20.69112075336635 + 20 +-24.14181793725912 + 10 +22.32591067616729 + 20 +-23.2040584739357 + 10 +22.32591067616729 + 20 +-23.2040584739357 + 10 +22.32591067616729 + 20 +-23.2040584739357 + 10 +22.73550754499572 + 20 +-22.97051689975263 + 10 +22.73550754499572 + 20 +-22.97051689975263 + 10 +22.73550754499572 + 20 +-22.97051689975263 + 10 +22.94030597941001 + 20 +-22.85195007760573 + 10 +22.94030597941001 + 20 +-22.85195007760573 + 10 +22.94030597941001 + 20 +-22.85195007760573 + 10 +23.04090806508131 + 20 +-22.79446270158767 + 10 +23.04090806508131 + 20 +-22.79446270158767 + 10 +23.04090806508131 + 20 +-22.79446270158767 + 10 +23.04809439826375 + 20 +-22.79086953499644 + 10 +23.04809439826375 + 20 +-22.79086953499644 + 10 +23.04809439826375 + 20 +-22.79086953499644 + 10 +23.04809439826375 + 20 +-22.79086953499644 + 10 +23.04809439826375 + 20 +-22.79086953499644 + 10 +23.04809439826375 + 20 +-22.79086953499644 + 10 +23.04809439826375 + 20 +-22.79086953499644 + 10 +23.04809439826375 + 20 +-22.79086953499644 + 10 +23.04809439826375 + 20 +-22.79086953499644 + 10 +23.06246706462869 + 20 +-22.78368320181398 + 10 +23.06246706462869 + 20 +-22.78368320181398 + 10 +23.06246706462869 + 20 +-22.78368320181398 + 10 +23.08761813428671 + 20 +-22.76931163192956 + 10 +23.08761813428671 + 20 +-22.76931163192956 + 10 +23.09121020439743 + 20 +-22.76931163192956 + 10 +23.0983965375799 + 20 +-22.76571846533837 + 10 +23.10199080065164 + 20 +-22.76571846533837 + 10 +23.10558287076234 + 20 +-22.76212529874715 + 10 +23.11276920394481 + 20 +-22.76212529874715 + 10 +23.11995553712728 + 20 +-22.7585321321559 + 10 +23.22415188587024 + 20 +-22.72619582579591 + 10 +23.34631077812782 + 20 +-22.75134579897344 + 10 +23.42535605721276 + 20 +-22.82679791146716 + 10 +23.46487760027478 + 20 +-22.8627273844184 + 10 +23.49721500311524 + 20 +-22.90943526066278 + 10 +23.5151797395909 + 20 +-22.96332947008967 + 10 +23.53314009014449 + 20 +-23.01363051292527 + 10 +23.53673216025522 + 20 +-23.07111788894341 + 10 +23.52954802003377 + 20 +-23.12501209837023 + 97 + 0 + 97 + 0 + 75 + 2 + 76 + 1 + 98 + 0 + 0 +ENDSEC + 0 +SECTION + 2 +OBJECTS + 0 +DICTIONARY + 5 +C +330 +0 +100 +AcDbDictionary +281 + 1 + 3 +ACAD_GROUP +350 +D + 3 +ACAD_LAYOUT +350 +1A + 3 +ACAD_MATERIAL +350 +3B + 3 +ACAD_MLEADERSTYLE +350 +47 + 3 +ACAD_MLINESTYLE +350 +17 + 3 +ACAD_PLOTSETTINGS +350 +19 + 3 +ACAD_PLOTSTYLENAME +350 +E + 3 +ACAD_TABLESTYLE +350 +46 + 3 +ACAD_VISUALSTYLE +350 +2A + 0 +SUN + 5 +3F +330 +29 +100 +AcDbSun + 90 + 1 +290 + 0 + 63 + 7 +421 + 16777215 + 40 +1.0 +291 + 1 + 91 + 2455826 + 92 + 54000000 +292 + 0 + 70 + 2 + 71 + 256 +280 + 1 + 0 +DICTIONARY + 5 +D +102 +{ACAD_REACTORS +330 +C +102 +} +330 +C +100 +AcDbDictionary +281 + 1 + 0 +DICTIONARY + 5 +1A +102 +{ACAD_REACTORS +330 +C +102 +} +330 +C +100 +AcDbDictionary +281 + 1 + 3 +Layout1 +350 +1E + 3 +Layout2 +350 +26 + 3 +Model +350 +22 + 0 +DICTIONARY + 5 +3B +102 +{ACAD_REACTORS +330 +C +102 +} +330 +C +100 +AcDbDictionary +281 + 1 + 3 +ByBlock +350 +3D + 3 +ByLayer +350 +3C + 3 +Global +350 +3E + 0 +DICTIONARY + 5 +47 +102 +{ACAD_REACTORS +330 +C +102 +} +330 +C +100 +AcDbDictionary +281 + 1 + 0 +DICTIONARY + 5 +17 +102 +{ACAD_REACTORS +330 +C +102 +} +330 +C +100 +AcDbDictionary +281 + 1 + 3 +Standard +350 +18 + 0 +DICTIONARY + 5 +19 +102 +{ACAD_REACTORS +330 +C +102 +} +330 +C +100 +AcDbDictionary +281 + 1 + 0 +ACDBDICTIONARYWDFLT + 5 +E +102 +{ACAD_REACTORS +330 +C +102 +} +330 +C +100 +AcDbDictionary +281 + 1 + 3 +Normal +350 +F +100 +AcDbDictionaryWithDefault +340 +F + 0 +DICTIONARY + 5 +46 +102 +{ACAD_REACTORS +330 +C +102 +} +330 +C +100 +AcDbDictionary +281 + 1 + 0 +DICTIONARY + 5 +2A +102 +{ACAD_REACTORS +330 +C +102 +} +330 +C +100 +AcDbDictionary +281 + 1 + 3 +2dWireframe +350 +2F + 3 +3D Hidden +350 +31 + 3 +3dWireframe +350 +30 + 3 +Basic +350 +32 + 3 +Brighten +350 +36 + 3 +ColorChange +350 +3A + 3 +Conceptual +350 +34 + 3 +Dim +350 +35 + 3 +Facepattern +350 +39 + 3 +Flat +350 +2B + 3 +FlatWithEdges +350 +2C + 3 +Gouraud +350 +2D + 3 +GouraudWithEdges +350 +2E + 3 +Linepattern +350 +38 + 3 +Realistic +350 +33 + 3 +Thicken +350 +37 + 0 +LAYOUT + 5 +1E +102 +{ACAD_REACTORS +330 +1A +102 +} +330 +1A +100 +AcDbPlotSettings + 1 + + 2 +none_device + 4 + + 6 + + 40 +0.0 + 41 +0.0 + 42 +0.0 + 43 +0.0 + 44 +0.0 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 + 49 +0.0 +140 +0.0 +141 +0.0 +142 +1.0 +143 +1.0 + 70 + 688 + 72 + 0 + 73 + 0 + 74 + 5 + 7 + + 75 + 16 + 76 + 0 + 77 + 2 + 78 + 300 +147 +1.0 +148 +0.0 +149 +0.0 +100 +AcDbLayout + 1 +Layout1 + 70 + 1 + 71 + 1 + 10 +0.0 + 20 +0.0 + 11 +12.0 + 21 +9.0 + 12 +0.0 + 22 +0.0 + 32 +0.0 + 14 +1.000000000000000E+20 + 24 +1.000000000000000E+20 + 34 +1.000000000000000E+20 + 15 +-1.000000000000000E+20 + 25 +-1.000000000000000E+20 + 35 +-1.000000000000000E+20 +146 +0.0 + 13 +0.0 + 23 +0.0 + 33 +0.0 + 16 +1.0 + 26 +0.0 + 36 +0.0 + 17 +0.0 + 27 +1.0 + 37 +0.0 + 76 + 0 +330 +1B + 0 +LAYOUT + 5 +26 +102 +{ACAD_REACTORS +330 +1A +102 +} +330 +1A +100 +AcDbPlotSettings + 1 + + 2 +none_device + 4 + + 6 + + 40 +0.0 + 41 +0.0 + 42 +0.0 + 43 +0.0 + 44 +0.0 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 + 49 +0.0 +140 +0.0 +141 +0.0 +142 +1.0 +143 +1.0 + 70 + 688 + 72 + 0 + 73 + 0 + 74 + 5 + 7 + + 75 + 16 + 76 + 0 + 77 + 2 + 78 + 300 +147 +1.0 +148 +0.0 +149 +0.0 +100 +AcDbLayout + 1 +Layout2 + 70 + 1 + 71 + 2 + 10 +0.0 + 20 +0.0 + 11 +0.0 + 21 +0.0 + 12 +0.0 + 22 +0.0 + 32 +0.0 + 14 +0.0 + 24 +0.0 + 34 +0.0 + 15 +0.0 + 25 +0.0 + 35 +0.0 +146 +0.0 + 13 +0.0 + 23 +0.0 + 33 +0.0 + 16 +1.0 + 26 +0.0 + 36 +0.0 + 17 +0.0 + 27 +1.0 + 37 +0.0 + 76 + 0 +330 +23 + 0 +LAYOUT + 5 +22 +102 +{ACAD_REACTORS +330 +1A +102 +} +330 +1A +100 +AcDbPlotSettings + 1 + + 2 +none_device + 4 + + 6 + + 40 +0.0 + 41 +0.0 + 42 +0.0 + 43 +0.0 + 44 +0.0 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 + 49 +0.0 +140 +0.0 +141 +0.0 +142 +1.0 +143 +1.0 + 70 + 1712 + 72 + 0 + 73 + 0 + 74 + 0 + 7 + + 75 + 0 + 76 + 0 + 77 + 2 + 78 + 300 +147 +1.0 +148 +0.0 +149 +0.0 +100 +AcDbLayout + 1 +Model + 70 + 1 + 71 + 0 + 10 +0.0 + 20 +0.0 + 11 +12.0 + 21 +9.0 + 12 +0.0 + 22 +0.0 + 32 +0.0 + 14 +1.000000000000000E+20 + 24 +1.000000000000000E+20 + 34 +1.000000000000000E+20 + 15 +-1.000000000000000E+20 + 25 +-1.000000000000000E+20 + 35 +-1.000000000000000E+20 +146 +0.0 + 13 +0.0 + 23 +0.0 + 33 +0.0 + 16 +1.0 + 26 +0.0 + 36 +0.0 + 17 +0.0 + 27 +1.0 + 37 +0.0 + 76 + 0 +330 +1F +331 +29 + 0 +MATERIAL + 5 +3D +102 +{ACAD_REACTORS +330 +3B +102 +} +330 +3B +100 +AcDbMaterial + 1 +ByBlock + 72 + 1 + 94 + 127 + 0 +MATERIAL + 5 +3C +102 +{ACAD_REACTORS +330 +3B +102 +} +330 +3B +100 +AcDbMaterial + 1 +ByLayer + 72 + 1 + 94 + 127 + 0 +MATERIAL + 5 +3E +102 +{ACAD_REACTORS +330 +3B +102 +} +330 +3B +100 +AcDbMaterial + 1 +Global + 72 + 1 + 94 + 127 + 0 +MLINESTYLE + 5 +18 +102 +{ACAD_REACTORS +330 +17 +102 +} +330 +17 +100 +AcDbMlineStyle + 2 +Standard + 70 + 0 + 3 + + 62 + 256 + 51 +90.0 + 52 +90.0 + 71 + 2 + 49 +0.5 + 62 + 256 + 6 +BYLAYER + 49 +-0.5 + 62 + 256 + 6 +BYLAYER + 0 +ACDBPLACEHOLDER + 5 +F +102 +{ACAD_REACTORS +330 +E +102 +} +330 +E + 0 +VISUALSTYLE + 5 +2F +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +2dWireframe + 70 + 4 +177 + 2 +291 + 0 + 71 + 0 +176 + 1 + 72 + 2 +176 + 1 + 73 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 1 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 0 +176 + 1 + 66 + 257 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 1 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +31 +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +3D Hidden + 70 + 6 +177 + 2 +291 + 0 + 71 + 1 +176 + 1 + 72 + 2 +176 + 1 + 73 + 2 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 2 +176 + 1 + 91 + 2 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 2 +176 + 1 +175 + 1 +176 + 1 + 42 +40.0 +176 + 1 + 92 + 0 +176 + 1 + 66 + 257 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 3 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 1 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +30 +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +3dWireframe + 70 + 5 +177 + 2 +291 + 0 + 71 + 0 +176 + 1 + 72 + 2 +176 + 1 + 73 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 1 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 0 +176 + 1 + 66 + 257 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 1 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +32 +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +Basic + 70 + 7 +177 + 2 +291 + 1 + 71 + 1 +176 + 1 + 72 + 0 +176 + 1 + 73 + 1 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 0 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 8 +176 + 1 + 66 + 7 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 1 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +36 +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +Brighten + 70 + 12 +177 + 2 +291 + 1 + 71 + 2 +176 + 1 + 72 + 2 +176 + 1 + 73 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 1 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 8 +176 + 1 + 66 + 7 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 1 +176 + 1 + 44 +50.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +3A +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +ColorChange + 70 + 16 +177 + 2 +291 + 1 + 71 + 2 +176 + 1 + 72 + 2 +176 + 1 + 73 + 3 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 8 +421 + 8421504 +176 + 1 + 74 + 1 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 8 +176 + 1 + 66 + 8 +424 + 8421504 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 1 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +34 +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +Conceptual + 70 + 9 +177 + 2 +291 + 0 + 71 + 3 +176 + 1 + 72 + 2 +176 + 1 + 73 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 2 +176 + 1 + 91 + 2 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +40.0 +176 + 1 + 92 + 8 +176 + 1 + 66 + 7 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 3 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 1 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +35 +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +Dim + 70 + 11 +177 + 2 +291 + 1 + 71 + 2 +176 + 1 + 72 + 2 +176 + 1 + 73 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 1 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 8 +176 + 1 + 66 + 7 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 1 +176 + 1 + 44 +-50.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +39 +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +Facepattern + 70 + 15 +177 + 2 +291 + 1 + 71 + 2 +176 + 1 + 72 + 2 +176 + 1 + 73 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 1 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 8 +176 + 1 + 66 + 7 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 1 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +2B +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +Flat + 70 + 0 +177 + 2 +291 + 1 + 71 + 2 +176 + 1 + 72 + 1 +176 + 1 + 73 + 1 +176 + 1 + 90 + 2 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 0 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 8 +176 + 1 + 66 + 7 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 13 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +2C +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +FlatWithEdges + 70 + 1 +177 + 2 +291 + 1 + 71 + 2 +176 + 1 + 72 + 1 +176 + 1 + 73 + 1 +176 + 1 + 90 + 2 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 1 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 0 +176 + 1 + 66 + 257 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 13 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +2D +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +Gouraud + 70 + 2 +177 + 2 +291 + 1 + 71 + 2 +176 + 1 + 72 + 2 +176 + 1 + 73 + 1 +176 + 1 + 90 + 2 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 0 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 0 +176 + 1 + 66 + 7 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 13 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +2E +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +GouraudWithEdges + 70 + 3 +177 + 2 +291 + 1 + 71 + 2 +176 + 1 + 72 + 2 +176 + 1 + 73 + 1 +176 + 1 + 90 + 2 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 1 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 0 +176 + 1 + 66 + 257 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 13 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +38 +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +Linepattern + 70 + 14 +177 + 2 +291 + 1 + 71 + 2 +176 + 1 + 72 + 2 +176 + 1 + 73 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 1 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 7 +176 + 1 +175 + 7 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 8 +176 + 1 + 66 + 7 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 1 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +33 +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +Realistic + 70 + 8 +177 + 2 +291 + 0 + 71 + 2 +176 + 1 + 72 + 2 +176 + 1 + 73 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 1 +176 + 1 + 91 + 0 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 8 +176 + 1 + 66 + 8 +424 + 7895160 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 13 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +37 +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +Thicken + 70 + 13 +177 + 2 +291 + 1 + 71 + 2 +176 + 1 + 72 + 2 +176 + 1 + 73 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 1 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 12 +176 + 1 + 66 + 7 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 1 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +ENDSEC + 0 +EOF diff --git a/Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 02.eps b/Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 02.eps new file mode 100644 index 0000000..3ac2db6 Binary files /dev/null and b/Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 02.eps differ diff --git a/Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 02.png b/Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 02.png new file mode 100644 index 0000000..1d7b422 Binary files /dev/null and b/Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 02.png differ diff --git a/Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 02.svg b/Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 02.svg new file mode 100644 index 0000000..b041c87 --- /dev/null +++ b/Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 02.svg @@ -0,0 +1,50 @@ + + + + + diff --git a/Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 03.dxf b/Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 03.dxf new file mode 100644 index 0000000..dcd2a98 --- /dev/null +++ b/Printing/Nameplates/Check-Mark/CF00089-08 Check Mark 03.dxf @@ -0,0 +1,6342 @@ + 0 +SECTION + 2 +HEADER + 9 +$ACADVER + 1 +AC1024 + 9 +$ACADMAINTVER + 70 + 6 + 9 +$DWGCODEPAGE + 3 +ANSI_1252 + 9 +$INSBASE + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$EXTMIN + 10 +1.000000000000000E+20 + 20 +1.000000000000000E+20 + 30 +1.000000000000000E+20 + 9 +$EXTMAX + 10 +-1.000000000000000E+20 + 20 +-1.000000000000000E+20 + 30 +-1.000000000000000E+20 + 9 +$LIMMIN + 10 +0.0 + 20 +0.0 + 9 +$LIMMAX + 10 +12.0 + 20 +9.0 + 9 +$ORTHOMODE + 70 + 0 + 9 +$REGENMODE + 70 + 1 + 9 +$FILLMODE + 70 + 1 + 9 +$QTEXTMODE + 70 + 0 + 9 +$MIRRTEXT + 70 + 1 + 9 +$LTSCALE + 40 +1.0 + 9 +$ATTMODE + 70 + 1 + 9 +$TEXTSIZE + 40 +0.2 + 9 +$TRACEWID + 40 +0.05 + 9 +$TEXTSTYLE + 7 +Standard + 9 +$CLAYER + 8 +0 + 9 +$CELTYPE + 6 +ByLayer + 9 +$CECOLOR + 62 + 256 + 9 +$CELTSCALE + 40 +1.0 + 9 +$DISPSILH + 70 + 0 + 9 +$DIMSCALE + 40 +1.0 + 9 +$DIMASZ + 40 +0.18 + 9 +$DIMEXO + 40 +0.0625 + 9 +$DIMDLI + 40 +0.38 + 9 +$DIMRND + 40 +0.0 + 9 +$DIMDLE + 40 +0.0 + 9 +$DIMEXE + 40 +0.18 + 9 +$DIMTP + 40 +0.0 + 9 +$DIMTM + 40 +0.0 + 9 +$DIMTXT + 40 +0.18 + 9 +$DIMCEN + 40 +0.09 + 9 +$DIMTSZ + 40 +0.0 + 9 +$DIMTOL + 70 + 0 + 9 +$DIMLIM + 70 + 0 + 9 +$DIMTIH + 70 + 1 + 9 +$DIMTOH + 70 + 1 + 9 +$DIMSE1 + 70 + 0 + 9 +$DIMSE2 + 70 + 0 + 9 +$DIMTAD + 70 + 0 + 9 +$DIMZIN + 70 + 0 + 9 +$DIMBLK + 1 + + 9 +$DIMASO + 70 + 1 + 9 +$DIMSHO + 70 + 1 + 9 +$DIMPOST + 1 + + 9 +$DIMAPOST + 1 + + 9 +$DIMALT + 70 + 0 + 9 +$DIMALTD + 70 + 2 + 9 +$DIMALTF + 40 +25.4 + 9 +$DIMLFAC + 40 +1.0 + 9 +$DIMTOFL + 70 + 0 + 9 +$DIMTVP + 40 +0.0 + 9 +$DIMTIX + 70 + 0 + 9 +$DIMSOXD + 70 + 0 + 9 +$DIMSAH + 70 + 0 + 9 +$DIMBLK1 + 1 + + 9 +$DIMBLK2 + 1 + + 9 +$DIMSTYLE + 2 +Standard + 9 +$DIMCLRD + 70 + 0 + 9 +$DIMCLRE + 70 + 0 + 9 +$DIMCLRT + 70 + 0 + 9 +$DIMTFAC + 40 +1.0 + 9 +$DIMGAP + 40 +0.09 + 9 +$DIMJUST + 70 + 0 + 9 +$DIMSD1 + 70 + 0 + 9 +$DIMSD2 + 70 + 0 + 9 +$DIMTOLJ + 70 + 1 + 9 +$DIMTZIN + 70 + 0 + 9 +$DIMALTZ + 70 + 0 + 9 +$DIMALTTZ + 70 + 0 + 9 +$DIMUPT + 70 + 0 + 9 +$DIMDEC + 70 + 4 + 9 +$DIMTDEC + 70 + 4 + 9 +$DIMALTU + 70 + 2 + 9 +$DIMALTTD + 70 + 2 + 9 +$DIMTXSTY + 7 +Standard + 9 +$DIMAUNIT + 70 + 0 + 9 +$DIMADEC + 70 + 0 + 9 +$DIMALTRND + 40 +0.0 + 9 +$DIMAZIN + 70 + 0 + 9 +$DIMDSEP + 70 + 46 + 9 +$DIMATFIT + 70 + 3 + 9 +$DIMFRAC + 70 + 0 + 9 +$DIMLDRBLK + 1 + + 9 +$DIMLUNIT + 70 + 2 + 9 +$DIMLWD + 70 + -2 + 9 +$DIMLWE + 70 + -2 + 9 +$DIMTMOVE + 70 + 0 + 9 +$DIMFXL + 40 +1.0 + 9 +$DIMFXLON + 70 + 0 + 9 +$DIMJOGANG + 40 +0.7853981633974483 + 9 +$DIMTFILL + 70 + 0 + 9 +$DIMTFILLCLR + 70 + 0 + 9 +$DIMARCSYM + 70 + 0 + 9 +$DIMLTYPE + 6 + + 9 +$DIMLTEX1 + 6 + + 9 +$DIMLTEX2 + 6 + + 9 +$DIMTXTDIRECTION + 70 + 0 + 9 +$LUNITS + 70 + 2 + 9 +$LUPREC + 70 + 4 + 9 +$SKETCHINC + 40 +0.1 + 9 +$FILLETRAD + 40 +0.5 + 9 +$AUNITS + 70 + 0 + 9 +$AUPREC + 70 + 0 + 9 +$MENU + 1 +. + 9 +$ELEVATION + 40 +0.0 + 9 +$PELEVATION + 40 +0.0 + 9 +$THICKNESS + 40 +0.0 + 9 +$LIMCHECK + 70 + 0 + 9 +$CHAMFERA + 40 +0.5 + 9 +$CHAMFERB + 40 +0.5 + 9 +$CHAMFERC + 40 +1.0 + 9 +$CHAMFERD + 40 +0.0 + 9 +$SKPOLY + 70 + 0 + 9 +$TDCREATE + 40 +2459543.541737627 + 9 +$TDUCREATE + 40 +2459543.291737627 + 9 +$TDUPDATE + 40 +2459543.541737651 + 9 +$TDUUPDATE + 40 +2459543.291737651 + 9 +$TDINDWG + 40 +0.0000000116 + 9 +$TDUSRTIMER + 40 +0.0000000116 + 9 +$USRTIMER + 70 + 1 + 9 +$ANGBASE + 50 +0.0 + 9 +$ANGDIR + 70 + 0 + 9 +$PDMODE + 70 + 0 + 9 +$PDSIZE + 40 +0.0 + 9 +$PLINEWID + 40 +0.0 + 9 +$SPLFRAME + 70 + 0 + 9 +$SPLINETYPE + 70 + 6 + 9 +$SPLINESEGS + 70 + 8 + 9 +$HANDSEED + 5 +4B + 9 +$SURFTAB1 + 70 + 6 + 9 +$SURFTAB2 + 70 + 6 + 9 +$SURFTYPE + 70 + 6 + 9 +$SURFU + 70 + 6 + 9 +$SURFV + 70 + 6 + 9 +$UCSBASE + 2 + + 9 +$UCSNAME + 2 + + 9 +$UCSORG + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSXDIR + 10 +1.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSYDIR + 10 +0.0 + 20 +1.0 + 30 +0.0 + 9 +$UCSORTHOREF + 2 + + 9 +$UCSORTHOVIEW + 70 + 0 + 9 +$UCSORGTOP + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGBOTTOM + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGLEFT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGRIGHT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGFRONT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$UCSORGBACK + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSBASE + 2 + + 9 +$PUCSNAME + 2 + + 9 +$PUCSORG + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSXDIR + 10 +1.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSYDIR + 10 +0.0 + 20 +1.0 + 30 +0.0 + 9 +$PUCSORTHOREF + 2 + + 9 +$PUCSORTHOVIEW + 70 + 0 + 9 +$PUCSORGTOP + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGBOTTOM + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGLEFT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGRIGHT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGFRONT + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PUCSORGBACK + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$USERI1 + 70 + 0 + 9 +$USERI2 + 70 + 0 + 9 +$USERI3 + 70 + 0 + 9 +$USERI4 + 70 + 0 + 9 +$USERI5 + 70 + 0 + 9 +$USERR1 + 40 +0.0 + 9 +$USERR2 + 40 +0.0 + 9 +$USERR3 + 40 +0.0 + 9 +$USERR4 + 40 +0.0 + 9 +$USERR5 + 40 +0.0 + 9 +$WORLDVIEW + 70 + 1 + 9 +$SHADEDGE + 70 + 3 + 9 +$SHADEDIF + 70 + 70 + 9 +$TILEMODE + 70 + 1 + 9 +$MAXACTVP + 70 + 64 + 9 +$PINSBASE + 10 +0.0 + 20 +0.0 + 30 +0.0 + 9 +$PLIMCHECK + 70 + 0 + 9 +$PEXTMIN + 10 +1.000000000000000E+20 + 20 +1.000000000000000E+20 + 30 +1.000000000000000E+20 + 9 +$PEXTMAX + 10 +-1.000000000000000E+20 + 20 +-1.000000000000000E+20 + 30 +-1.000000000000000E+20 + 9 +$PLIMMIN + 10 +0.0 + 20 +0.0 + 9 +$PLIMMAX + 10 +12.0 + 20 +9.0 + 9 +$UNITMODE + 70 + 0 + 9 +$VISRETAIN + 70 + 1 + 9 +$PLINEGEN + 70 + 0 + 9 +$PSLTSCALE + 70 + 1 + 9 +$TREEDEPTH + 70 + 3020 + 9 +$CMLSTYLE + 2 +Standard + 9 +$CMLJUST + 70 + 0 + 9 +$CMLSCALE + 40 +1.0 + 9 +$PROXYGRAPHICS + 70 + 1 + 9 +$MEASUREMENT + 70 + 0 + 9 +$CELWEIGHT +370 + -1 + 9 +$ENDCAPS +280 + 0 + 9 +$JOINSTYLE +280 + 0 + 9 +$LWDISPLAY +290 + 1 + 9 +$INSUNITS + 70 + 1 + 9 +$HYPERLINKBASE + 1 + + 9 +$STYLESHEET + 1 + + 9 +$XEDIT +290 + 1 + 9 +$CEPSNTYPE +380 + 0 + 9 +$PSTYLEMODE +290 + 1 + 9 +$FINGERPRINTGUID + 2 +{0BE043D2-9EA7-4B27-935B-AE860C928389} + 9 +$VERSIONGUID + 2 +{FAEB1C32-E019-11D5-929B-00C0DF256EC4} + 9 +$EXTNAMES +290 + 1 + 9 +$PSVPSCALE + 40 +0.0 + 9 +$OLESTARTUP +290 + 0 + 9 +$SORTENTS +280 + 127 + 9 +$INDEXCTL +280 + 0 + 9 +$HIDETEXT +280 + 1 + 9 +$XCLIPFRAME +280 + 2 + 9 +$HALOGAP +280 + 0 + 9 +$OBSCOLOR + 70 + 257 + 9 +$OBSLTYPE +280 + 0 + 9 +$INTERSECTIONDISPLAY +280 + 0 + 9 +$INTERSECTIONCOLOR + 70 + 257 + 9 +$DIMASSOC +280 + 2 + 9 +$PROJECTNAME + 1 + + 9 +$CAMERADISPLAY +290 + 0 + 9 +$LENSLENGTH + 40 +50.0 + 9 +$CAMERAHEIGHT + 40 +0.0 + 9 +$STEPSPERSEC + 40 +2.0 + 9 +$STEPSIZE + 40 +6.0 + 9 +$3DDWFPREC + 40 +2.0 + 9 +$PSOLWIDTH + 40 +0.25 + 9 +$PSOLHEIGHT + 40 +4.0 + 9 +$LOFTANG1 + 40 +1.570796326794897 + 9 +$LOFTANG2 + 40 +1.570796326794897 + 9 +$LOFTMAG1 + 40 +0.0 + 9 +$LOFTMAG2 + 40 +0.0 + 9 +$LOFTPARAM + 70 + 7 + 9 +$LOFTNORMALS +280 + 1 + 9 +$LATITUDE + 40 +37.795 + 9 +$LONGITUDE + 40 +-122.394 + 9 +$NORTHDIRECTION + 40 +0.0 + 9 +$TIMEZONE + 70 + -8000 + 9 +$LIGHTGLYPHDISPLAY +280 + 1 + 9 +$TILEMODELIGHTSYNCH +280 + 1 + 9 +$CMATERIAL +347 +3C + 9 +$SOLIDHIST +280 + 1 + 9 +$SHOWHIST +280 + 1 + 9 +$DWFFRAME +280 + 2 + 9 +$DGNFRAME +280 + 2 + 9 +$REALWORLDSCALE +290 + 1 + 9 +$INTERFERECOLOR + 62 + 256 + 9 +$CSHADOW +280 + 0 + 9 +$SHADOWPLANELOCATION + 40 +0.0 + 0 +ENDSEC + 0 +SECTION + 2 +CLASSES + 0 +CLASS + 1 +ACDBDICTIONARYWDFLT + 2 +AcDbDictionaryWithDefault + 3 +ObjectDBX Classes + 90 + 0 + 91 + 4 +280 + 0 +281 + 0 + 0 +CLASS + 1 +VISUALSTYLE + 2 +AcDbVisualStyle + 3 +ObjectDBX Classes + 90 + 4095 + 91 + 4 +280 + 0 +281 + 0 + 0 +CLASS + 1 +MATERIAL + 2 +AcDbMaterial + 3 +ObjectDBX Classes + 90 + 1153 + 91 + 4 +280 + 0 +281 + 0 + 0 +CLASS + 1 +SUN + 2 +AcDbSun + 3 +SCENEOE + 90 + 1024 + 91 + 4 +280 + 0 +281 + 0 + 0 +ENDSEC + 0 +SECTION + 2 +TABLES + 0 +TABLE + 2 +VPORT + 5 +8 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +VPORT + 5 +29 +330 +8 +100 +AcDbSymbolTableRecord +100 +AcDbViewportTableRecord + 2 +*Active + 70 + 0 + 10 +0.0 + 20 +0.0 + 11 +1.0 + 21 +1.0 + 12 +0.0 + 22 +0.0 + 13 +0.0 + 23 +0.0 + 14 +0.5 + 24 +0.5 + 15 +0.5 + 25 +0.5 + 16 +0.0 + 26 +0.0 + 36 +1.0 + 17 +20.45307307386135 + 27 +-27.57204415910634 + 37 +0.0 + 40 +7.897947847549478 + 41 +1.26615168813792 + 42 +50.0 + 43 +0.0 + 44 +0.0 + 50 +0.0 + 51 +0.0 + 71 + 0 + 72 + 100 + 73 + 1 + 74 + 3 + 75 + 0 + 76 + 0 + 77 + 0 + 78 + 0 +281 + 0 + 65 + 1 +110 +0.0 +120 +0.0 +130 +0.0 +111 +1.0 +121 +0.0 +131 +0.0 +112 +0.0 +122 +1.0 +132 +0.0 + 79 + 0 +146 +0.0 + 60 + 3 + 61 + 5 +292 + 1 +282 + 1 +141 +0.0 +142 +0.0 + 63 + 250 +361 +3F + 0 +ENDTAB + 0 +TABLE + 2 +LTYPE + 5 +5 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +LTYPE + 5 +14 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +ByBlock + 70 + 0 + 3 + + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +LTYPE + 5 +15 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +ByLayer + 70 + 0 + 3 + + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +LTYPE + 5 +16 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +Continuous + 70 + 0 + 3 +Solid line + 72 + 65 + 73 + 0 + 40 +0.0 + 0 +ENDTAB + 0 +TABLE + 2 +LAYER + 5 +2 +330 +0 +100 +AcDbSymbolTable + 70 + 2 + 0 +LAYER + 5 +10 +330 +2 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +0 + 70 + 0 + 62 + 7 + 6 +Continuous +370 + -3 +390 +F +347 +3E + 0 +LAYER + 5 +40 +330 +2 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +Layer 1 + 70 + 0 + 62 + 7 + 6 +Continuous +370 + -3 +390 +F +347 +3E + 0 +ENDTAB + 0 +TABLE + 2 +STYLE + 5 +3 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +STYLE + 5 +11 +330 +3 +100 +AcDbSymbolTableRecord +100 +AcDbTextStyleTableRecord + 2 +Standard + 70 + 0 + 40 +0.0 + 41 +1.0 + 50 +0.0 + 71 + 0 + 42 +0.2 + 3 +txt + 4 + + 0 +ENDTAB + 0 +TABLE + 2 +VIEW + 5 +6 +330 +0 +100 +AcDbSymbolTable + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +UCS + 5 +7 +330 +0 +100 +AcDbSymbolTable + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +APPID + 5 +9 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +APPID + 5 +12 +330 +9 +100 +AcDbSymbolTableRecord +100 +AcDbRegAppTableRecord + 2 +ACAD + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +DIMSTYLE + 5 +A +330 +0 +100 +AcDbSymbolTable + 70 + 1 +100 +AcDbDimStyleTable + 0 +DIMSTYLE +105 +27 +330 +A +100 +AcDbSymbolTableRecord +100 +AcDbDimStyleTableRecord + 2 +Standard + 70 + 0 +178 + 0 +340 +11 + 0 +ENDTAB + 0 +TABLE + 2 +BLOCK_RECORD + 5 +1 +330 +0 +100 +AcDbSymbolTable + 70 + 2 + 0 +BLOCK_RECORD + 5 +1F +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*Model_Space +340 +22 + 70 + 0 +280 + 1 +281 + 0 + 0 +BLOCK_RECORD + 5 +1B +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*Paper_Space +340 +1E + 70 + 0 +280 + 1 +281 + 0 + 0 +BLOCK_RECORD + 5 +23 +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*Paper_Space0 +340 +26 + 70 + 0 +280 + 1 +281 + 0 + 0 +BLOCK_RECORD + 5 +41 +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +block 2 +340 +0 +102 +{BLKREFS +331 +42 +102 +} + 70 + 0 +280 + 1 +281 + 0 + 0 +ENDTAB + 0 +ENDSEC + 0 +SECTION + 2 +BLOCKS + 0 +BLOCK + 5 +20 +330 +1F +100 +AcDbEntity + 8 +0 +100 +AcDbBlockBegin + 2 +*Model_Space + 70 + 0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +*Model_Space + 1 + + 0 +ENDBLK + 5 +21 +330 +1F +100 +AcDbEntity + 8 +0 +100 +AcDbBlockEnd + 0 +BLOCK + 5 +1C +330 +1B +100 +AcDbEntity + 67 + 1 + 8 +0 +100 +AcDbBlockBegin + 2 +*Paper_Space + 70 + 0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +*Paper_Space + 1 + + 0 +ENDBLK + 5 +1D +330 +1B +100 +AcDbEntity + 67 + 1 + 8 +0 +100 +AcDbBlockEnd + 0 +BLOCK + 5 +24 +330 +23 +100 +AcDbEntity + 8 +0 +100 +AcDbBlockBegin + 2 +*Paper_Space0 + 70 + 0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +*Paper_Space0 + 1 + + 0 +ENDBLK + 5 +25 +330 +23 +100 +AcDbEntity + 8 +0 +100 +AcDbBlockEnd + 0 +BLOCK + 5 +43 +330 +41 +100 +AcDbEntity + 8 +0 +100 +AcDbBlockBegin + 2 +block 2 + 70 + 0 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 3 +block 2 + 1 + + 0 +SPLINE + 5 +45 +330 +41 +100 +AcDbEntity + 8 +Layer 1 +370 + 0 +100 +AcDbSpline +210 +0.0 +220 +0.0 +230 +1.0 + 70 + 11 + 71 + 3 + 72 + 56 + 73 + 52 + 74 + 0 + 42 +0.0000000001 + 43 +0.0000000001 + 12 +1.0 + 22 +0.0 + 32 +0.0 + 13 +1.0 + 23 +0.0 + 33 +0.0 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +1.0 + 40 +1.0 + 40 +1.0 + 40 +2.0 + 40 +2.0 + 40 +2.0 + 40 +3.0 + 40 +3.0 + 40 +3.0 + 40 +4.0 + 40 +4.0 + 40 +4.0 + 40 +5.0 + 40 +5.0 + 40 +5.0 + 40 +6.0 + 40 +6.0 + 40 +6.0 + 40 +7.0 + 40 +7.0 + 40 +7.0 + 40 +8.0 + 40 +8.0 + 40 +8.0 + 40 +9.0 + 40 +9.0 + 40 +9.0 + 40 +10.0 + 40 +10.0 + 40 +10.0 + 40 +11.0 + 40 +11.0 + 40 +11.0 + 40 +12.0 + 40 +12.0 + 40 +12.0 + 40 +13.0 + 40 +13.0 + 40 +13.0 + 40 +14.0 + 40 +14.0 + 40 +14.0 + 40 +15.0 + 40 +15.0 + 40 +15.0 + 40 +16.0 + 40 +16.0 + 40 +16.0 + 40 +17.0 + 40 +17.0 + 40 +17.0 + 40 +17.0 + 10 +22.53535441724984 + 20 +-30.56162187554259 + 30 +0.0 + 10 +22.53535441724984 + 20 +-30.56162187554259 + 30 +0.0 + 10 +16.41246928119982 + 20 +-30.56162187554259 + 30 +0.0 + 10 +16.41246928119982 + 20 +-30.56162187554259 + 30 +0.0 + 10 +16.41246928119982 + 20 +-30.56162187554259 + 30 +0.0 + 10 +16.41246928119982 + 20 +-24.58246644267009 + 30 +0.0 + 10 +16.41246928119982 + 20 +-24.58246644267009 + 30 +0.0 + 10 +16.41246928119982 + 20 +-24.58246644267009 + 30 +0.0 + 10 +22.53894787914345 + 20 +-24.58246644267009 + 30 +0.0 + 10 +22.53894787914345 + 20 +-24.58246644267009 + 30 +0.0 + 10 +22.53894787914345 + 20 +-24.58246644267009 + 30 +0.0 + 10 +22.53894787914345 + 20 +-25.99820379965498 + 30 +0.0 + 10 +22.53894787914345 + 20 +-25.99820379965498 + 30 +0.0 + 10 +22.53894787914345 + 20 +-25.99820379965498 + 30 +0.0 + 10 +23.49475062458833 + 20 +-25.4412511998419 + 30 +0.0 + 10 +23.49475062458833 + 20 +-25.4412511998419 + 30 +0.0 + 10 +23.49475062458833 + 20 +-25.4412511998419 + 30 +0.0 + 10 +23.49475062458833 + 20 +-24.485448454397 + 30 +0.0 + 10 +23.49475062458833 + 20 +-24.485448454397 + 30 +0.0 + 10 +23.49475062458833 + 20 +-24.01113999528277 + 30 +0.0 + 10 +23.11027322996013 + 20 +-23.6230702353316 + 30 +0.0 + 10 +22.632372405523 + 20 +-23.6230702353316 + 30 +0.0 + 10 +22.632372405523 + 20 +-23.6230702353316 + 30 +0.0 + 10 +16.31545129292666 + 20 +-23.6230702353316 + 30 +0.0 + 10 +16.31545129292666 + 20 +-23.6230702353316 + 30 +0.0 + 10 +15.84114283381248 + 20 +-23.6230702353316 + 30 +0.0 + 10 +15.45307307386133 + 20 +-24.00754762995984 + 30 +0.0 + 10 +15.45307307386133 + 20 +-24.485448454397 + 30 +0.0 + 10 +15.45307307386133 + 20 +-24.485448454397 + 30 +0.0 + 10 +15.45307307386133 + 20 +-30.6586398638157 + 30 +0.0 + 10 +15.45307307386133 + 20 +-30.6586398638157 + 30 +0.0 + 10 +15.45307307386133 + 20 +-31.13294832292988 + 30 +0.0 + 10 +15.83755046848953 + 20 +-31.52101808288108 + 30 +0.0 + 10 +16.31545129292666 + 20 +-31.52101808288108 + 30 +0.0 + 10 +16.31545129292666 + 20 +-31.52101808288108 + 30 +0.0 + 10 +22.632372405523 + 20 +-31.52101808288108 + 30 +0.0 + 10 +22.632372405523 + 20 +-31.52101808288108 + 30 +0.0 + 10 +23.10668086463718 + 20 +-31.52101808288108 + 30 +0.0 + 10 +23.49475062458833 + 20 +-31.13654068825283 + 30 +0.0 + 10 +23.49475062458833 + 20 +-30.6586398638157 + 30 +0.0 + 10 +23.49475062458833 + 20 +-30.6586398638157 + 30 +0.0 + 10 +23.49475062458833 + 20 +-27.10132971017103 + 30 +0.0 + 10 +23.49475062458833 + 20 +-27.10132971017103 + 30 +0.0 + 10 +23.49475062458833 + 20 +-27.10132971017103 + 30 +0.0 + 10 +22.53894787914345 + 20 +-27.65828230998409 + 30 +0.0 + 10 +22.53894787914345 + 20 +-27.65828230998409 + 30 +0.0 + 10 +22.53894787914345 + 20 +-27.65828230998409 + 30 +0.0 + 10 +22.53894787914345 + 20 +-30.56162187554259 + 30 +0.0 + 10 +22.53894787914345 + 20 +-30.56162187554259 + 30 +0.0 + 10 +22.53894787914345 + 20 +-30.56162187554259 + 30 +0.0 + 10 +22.53535441724984 + 20 +-30.56162187554259 + 30 +0.0 + 10 +22.53535441724984 + 20 +-30.56162187554259 + 30 +0.0 + 0 +HATCH + 5 +46 +330 +41 +100 +AcDbEntity + 8 +Layer 1 + 62 + 250 +420 + 2301728 +100 +AcDbHatch + 10 +0.0 + 20 +0.0 + 30 +0.0 +210 +0.0 +220 +0.0 +230 +1.0 + 2 +SOLID + 70 + 1 + 71 + 0 + 91 + 1 + 92 + 1 + 93 + 1 + 72 + 4 + 94 + 3 + 73 + 0 + 74 + 1 + 95 + 56 + 96 + 52 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +1.0 + 40 +1.0 + 40 +1.0 + 40 +2.0 + 40 +2.0 + 40 +2.0 + 40 +3.0 + 40 +3.0 + 40 +3.0 + 40 +4.0 + 40 +4.0 + 40 +4.0 + 40 +5.0 + 40 +5.0 + 40 +5.0 + 40 +6.0 + 40 +6.0 + 40 +6.0 + 40 +7.0 + 40 +7.0 + 40 +7.0 + 40 +8.0 + 40 +8.0 + 40 +8.0 + 40 +9.0 + 40 +9.0 + 40 +9.0 + 40 +10.0 + 40 +10.0 + 40 +10.0 + 40 +11.0 + 40 +11.0 + 40 +11.0 + 40 +12.0 + 40 +12.0 + 40 +12.0 + 40 +13.0 + 40 +13.0 + 40 +13.0 + 40 +14.0 + 40 +14.0 + 40 +14.0 + 40 +15.0 + 40 +15.0 + 40 +15.0 + 40 +16.0 + 40 +16.0 + 40 +16.0 + 40 +17.0 + 40 +17.0 + 40 +17.0 + 40 +17.0 + 10 +22.53535441724984 + 20 +-30.56162187554259 + 10 +22.53535441724984 + 20 +-30.56162187554259 + 10 +16.41246928119982 + 20 +-30.56162187554259 + 10 +16.41246928119982 + 20 +-30.56162187554259 + 10 +16.41246928119982 + 20 +-30.56162187554259 + 10 +16.41246928119982 + 20 +-24.58246644267009 + 10 +16.41246928119982 + 20 +-24.58246644267009 + 10 +16.41246928119982 + 20 +-24.58246644267009 + 10 +22.53894787914345 + 20 +-24.58246644267009 + 10 +22.53894787914345 + 20 +-24.58246644267009 + 10 +22.53894787914345 + 20 +-24.58246644267009 + 10 +22.53894787914345 + 20 +-25.99820379965498 + 10 +22.53894787914345 + 20 +-25.99820379965498 + 10 +22.53894787914345 + 20 +-25.99820379965498 + 10 +23.49475062458833 + 20 +-25.4412511998419 + 10 +23.49475062458833 + 20 +-25.4412511998419 + 10 +23.49475062458833 + 20 +-25.4412511998419 + 10 +23.49475062458833 + 20 +-24.485448454397 + 10 +23.49475062458833 + 20 +-24.485448454397 + 10 +23.49475062458833 + 20 +-24.01113999528277 + 10 +23.11027322996013 + 20 +-23.6230702353316 + 10 +22.632372405523 + 20 +-23.6230702353316 + 10 +22.632372405523 + 20 +-23.6230702353316 + 10 +16.31545129292666 + 20 +-23.6230702353316 + 10 +16.31545129292666 + 20 +-23.6230702353316 + 10 +15.84114283381248 + 20 +-23.6230702353316 + 10 +15.45307307386133 + 20 +-24.00754762995984 + 10 +15.45307307386133 + 20 +-24.485448454397 + 10 +15.45307307386133 + 20 +-24.485448454397 + 10 +15.45307307386133 + 20 +-30.6586398638157 + 10 +15.45307307386133 + 20 +-30.6586398638157 + 10 +15.45307307386133 + 20 +-31.13294832292988 + 10 +15.83755046848953 + 20 +-31.52101808288108 + 10 +16.31545129292666 + 20 +-31.52101808288108 + 10 +16.31545129292666 + 20 +-31.52101808288108 + 10 +22.632372405523 + 20 +-31.52101808288108 + 10 +22.632372405523 + 20 +-31.52101808288108 + 10 +23.10668086463718 + 20 +-31.52101808288108 + 10 +23.49475062458833 + 20 +-31.13654068825283 + 10 +23.49475062458833 + 20 +-30.6586398638157 + 10 +23.49475062458833 + 20 +-30.6586398638157 + 10 +23.49475062458833 + 20 +-27.10132971017103 + 10 +23.49475062458833 + 20 +-27.10132971017103 + 10 +23.49475062458833 + 20 +-27.10132971017103 + 10 +22.53894787914345 + 20 +-27.65828230998409 + 10 +22.53894787914345 + 20 +-27.65828230998409 + 10 +22.53894787914345 + 20 +-27.65828230998409 + 10 +22.53894787914345 + 20 +-30.56162187554259 + 10 +22.53894787914345 + 20 +-30.56162187554259 + 10 +22.53894787914345 + 20 +-30.56162187554259 + 10 +22.53535441724984 + 20 +-30.56162187554259 + 10 +22.53535441724984 + 20 +-30.56162187554259 + 97 + 0 + 97 + 0 + 75 + 2 + 76 + 1 + 98 + 0 + 0 +SPLINE + 5 +47 +330 +41 +100 +AcDbEntity + 8 +Layer 1 +370 + 0 +100 +AcDbSpline +210 +0.0 +220 +0.0 +230 +1.0 + 70 + 11 + 71 + 3 + 72 + 35 + 73 + 31 + 74 + 0 + 42 +0.0000000001 + 43 +0.0000000001 + 12 +1.0 + 22 +0.0 + 32 +0.0 + 13 +1.0 + 23 +0.0 + 33 +0.0 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +1.0 + 40 +1.0 + 40 +1.0 + 40 +2.0 + 40 +2.0 + 40 +2.0 + 40 +3.0 + 40 +3.0 + 40 +3.0 + 40 +4.0 + 40 +4.0 + 40 +4.0 + 40 +5.0 + 40 +5.0 + 40 +5.0 + 40 +6.0 + 40 +6.0 + 40 +6.0 + 40 +7.0 + 40 +7.0 + 40 +7.0 + 40 +8.0 + 40 +8.0 + 40 +8.0 + 40 +9.0 + 40 +9.0 + 40 +9.0 + 40 +10.0 + 40 +10.0 + 40 +10.0 + 40 +10.0 + 10 +25.38838990258101 + 20 +-25.17175800638397 + 30 +0.0 + 10 +25.25543948851345 + 20 +-24.94179070061401 + 30 +0.0 + 10 +24.96438771683544 + 20 +-24.86633238713165 + 30 +0.0 + 10 +24.73441931449482 + 20 +-24.99928280119914 + 30 +0.0 + 10 +24.73441931449482 + 20 +-24.99928280119914 + 30 +0.0 + 10 +18.94211183781082 + 20 +-28.36615153676181 + 30 +0.0 + 10 +18.94211183781082 + 20 +-28.36615153676181 + 30 +0.0 + 10 +18.94211183781082 + 20 +-28.36615153676181 + 30 +0.0 + 10 +17.94678320467791 + 20 +-27.13366977064248 + 30 +0.0 + 10 +17.94678320467791 + 20 +-27.13366977064248 + 30 +0.0 + 10 +17.78149382670959 + 20 +-26.92885450498619 + 30 +0.0 + 10 +17.47966166935086 + 20 +-26.89651554108536 + 30 +0.0 + 10 +17.27484640369458 + 20 +-27.06180491905368 + 30 +0.0 + 10 +17.0700311380383 + 20 +-27.227094297022 + 30 +0.0 + 10 +17.03769217413747 + 20 +-27.52892645438073 + 30 +0.0 + 10 +17.20298155210579 + 20 +-27.73374172003701 + 30 +0.0 + 10 +17.20298155210579 + 20 +-27.73374172003701 + 30 +0.0 + 10 +18.45702299301587 + 20 +-29.28602295298295 + 30 +0.0 + 10 +18.45702299301587 + 20 +-29.28602295298295 + 30 +0.0 + 10 +18.55044751939542 + 20 +-29.40100715415322 + 30 +0.0 + 10 +18.69058376067945 + 20 +-29.46209162006135 + 30 +0.0 + 10 +18.83072000196342 + 20 +-29.46209162006135 + 30 +0.0 + 10 +18.91336414266228 + 20 +-29.46209162006135 + 30 +0.0 + 10 +18.99600937993172 + 20 +-29.44053194527057 + 30 +0.0 + 10 +19.07146769341413 + 20 +-29.39741369225966 + 30 +0.0 + 10 +19.07146769341413 + 20 +-29.39741369225966 + 30 +0.0 + 10 +25.2159125042549 + 20 +-25.82573078761137 + 30 +0.0 + 10 +25.2159125042549 + 20 +-25.82573078761137 + 30 +0.0 + 10 +25.4422885412726 + 20 +-25.69277818040266 + 30 +0.0 + 10 +25.52133812350727 + 20 +-25.39813294683103 + 30 +0.0 + 10 +25.38838990258101 + 20 +-25.17175800638397 + 30 +0.0 + 0 +HATCH + 5 +48 +330 +41 +100 +AcDbEntity + 8 +Layer 1 + 62 + 250 +420 + 2301728 +100 +AcDbHatch + 10 +0.0 + 20 +0.0 + 30 +0.0 +210 +0.0 +220 +0.0 +230 +1.0 + 2 +SOLID + 70 + 1 + 71 + 0 + 91 + 1 + 92 + 1 + 93 + 1 + 72 + 4 + 94 + 3 + 73 + 0 + 74 + 1 + 95 + 35 + 96 + 31 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +0.0 + 40 +1.0 + 40 +1.0 + 40 +1.0 + 40 +2.0 + 40 +2.0 + 40 +2.0 + 40 +3.0 + 40 +3.0 + 40 +3.0 + 40 +4.0 + 40 +4.0 + 40 +4.0 + 40 +5.0 + 40 +5.0 + 40 +5.0 + 40 +6.0 + 40 +6.0 + 40 +6.0 + 40 +7.0 + 40 +7.0 + 40 +7.0 + 40 +8.0 + 40 +8.0 + 40 +8.0 + 40 +9.0 + 40 +9.0 + 40 +9.0 + 40 +10.0 + 40 +10.0 + 40 +10.0 + 40 +10.0 + 10 +25.38838990258101 + 20 +-25.17175800638397 + 10 +25.25543948851345 + 20 +-24.94179070061401 + 10 +24.96438771683544 + 20 +-24.86633238713165 + 10 +24.73441931449482 + 20 +-24.99928280119914 + 10 +24.73441931449482 + 20 +-24.99928280119914 + 10 +18.94211183781082 + 20 +-28.36615153676181 + 10 +18.94211183781082 + 20 +-28.36615153676181 + 10 +18.94211183781082 + 20 +-28.36615153676181 + 10 +17.94678320467791 + 20 +-27.13366977064248 + 10 +17.94678320467791 + 20 +-27.13366977064248 + 10 +17.78149382670959 + 20 +-26.92885450498619 + 10 +17.47966166935086 + 20 +-26.89651554108536 + 10 +17.27484640369458 + 20 +-27.06180491905368 + 10 +17.0700311380383 + 20 +-27.227094297022 + 10 +17.03769217413747 + 20 +-27.52892645438073 + 10 +17.20298155210579 + 20 +-27.73374172003701 + 10 +17.20298155210579 + 20 +-27.73374172003701 + 10 +18.45702299301587 + 20 +-29.28602295298295 + 10 +18.45702299301587 + 20 +-29.28602295298295 + 10 +18.55044751939542 + 20 +-29.40100715415322 + 10 +18.69058376067945 + 20 +-29.46209162006135 + 10 +18.83072000196342 + 20 +-29.46209162006135 + 10 +18.91336414266228 + 20 +-29.46209162006135 + 10 +18.99600937993172 + 20 +-29.44053194527057 + 10 +19.07146769341413 + 20 +-29.39741369225966 + 10 +19.07146769341413 + 20 +-29.39741369225966 + 10 +25.2159125042549 + 20 +-25.82573078761137 + 10 +25.2159125042549 + 20 +-25.82573078761137 + 10 +25.4422885412726 + 20 +-25.69277818040266 + 10 +25.52133812350727 + 20 +-25.39813294683103 + 10 +25.38838990258101 + 20 +-25.17175800638397 + 97 + 0 + 97 + 0 + 75 + 2 + 76 + 1 + 98 + 0 + 0 +ENDBLK + 5 +44 +330 +41 +100 +AcDbEntity + 8 +0 +100 +AcDbBlockEnd + 0 +ENDSEC + 0 +SECTION + 2 +ENTITIES + 0 +INSERT + 5 +42 +330 +1F +100 +AcDbEntity + 8 +Layer 1 +100 +AcDbBlockReference + 2 +block 2 + 10 +0.0 + 20 +0.0 + 30 +0.0 + 0 +ENDSEC + 0 +SECTION + 2 +OBJECTS + 0 +DICTIONARY + 5 +C +330 +0 +100 +AcDbDictionary +281 + 1 + 3 +ACAD_GROUP +350 +D + 3 +ACAD_LAYOUT +350 +1A + 3 +ACAD_MATERIAL +350 +3B + 3 +ACAD_MLEADERSTYLE +350 +4A + 3 +ACAD_MLINESTYLE +350 +17 + 3 +ACAD_PLOTSETTINGS +350 +19 + 3 +ACAD_PLOTSTYLENAME +350 +E + 3 +ACAD_TABLESTYLE +350 +49 + 3 +ACAD_VISUALSTYLE +350 +2A + 0 +SUN + 5 +3F +330 +29 +100 +AcDbSun + 90 + 1 +290 + 0 + 63 + 7 +421 + 16777215 + 40 +1.0 +291 + 1 + 91 + 2455826 + 92 + 54000000 +292 + 0 + 70 + 2 + 71 + 256 +280 + 1 + 0 +DICTIONARY + 5 +D +102 +{ACAD_REACTORS +330 +C +102 +} +330 +C +100 +AcDbDictionary +281 + 1 + 0 +DICTIONARY + 5 +1A +102 +{ACAD_REACTORS +330 +C +102 +} +330 +C +100 +AcDbDictionary +281 + 1 + 3 +Layout1 +350 +1E + 3 +Layout2 +350 +26 + 3 +Model +350 +22 + 0 +DICTIONARY + 5 +3B +102 +{ACAD_REACTORS +330 +C +102 +} +330 +C +100 +AcDbDictionary +281 + 1 + 3 +ByBlock +350 +3D + 3 +ByLayer +350 +3C + 3 +Global +350 +3E + 0 +DICTIONARY + 5 +4A +102 +{ACAD_REACTORS +330 +C +102 +} +330 +C +100 +AcDbDictionary +281 + 1 + 0 +DICTIONARY + 5 +17 +102 +{ACAD_REACTORS +330 +C +102 +} +330 +C +100 +AcDbDictionary +281 + 1 + 3 +Standard +350 +18 + 0 +DICTIONARY + 5 +19 +102 +{ACAD_REACTORS +330 +C +102 +} +330 +C +100 +AcDbDictionary +281 + 1 + 0 +ACDBDICTIONARYWDFLT + 5 +E +102 +{ACAD_REACTORS +330 +C +102 +} +330 +C +100 +AcDbDictionary +281 + 1 + 3 +Normal +350 +F +100 +AcDbDictionaryWithDefault +340 +F + 0 +DICTIONARY + 5 +49 +102 +{ACAD_REACTORS +330 +C +102 +} +330 +C +100 +AcDbDictionary +281 + 1 + 0 +DICTIONARY + 5 +2A +102 +{ACAD_REACTORS +330 +C +102 +} +330 +C +100 +AcDbDictionary +281 + 1 + 3 +2dWireframe +350 +2F + 3 +3D Hidden +350 +31 + 3 +3dWireframe +350 +30 + 3 +Basic +350 +32 + 3 +Brighten +350 +36 + 3 +ColorChange +350 +3A + 3 +Conceptual +350 +34 + 3 +Dim +350 +35 + 3 +Facepattern +350 +39 + 3 +Flat +350 +2B + 3 +FlatWithEdges +350 +2C + 3 +Gouraud +350 +2D + 3 +GouraudWithEdges +350 +2E + 3 +Linepattern +350 +38 + 3 +Realistic +350 +33 + 3 +Thicken +350 +37 + 0 +LAYOUT + 5 +1E +102 +{ACAD_REACTORS +330 +1A +102 +} +330 +1A +100 +AcDbPlotSettings + 1 + + 2 +none_device + 4 + + 6 + + 40 +0.0 + 41 +0.0 + 42 +0.0 + 43 +0.0 + 44 +0.0 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 + 49 +0.0 +140 +0.0 +141 +0.0 +142 +1.0 +143 +1.0 + 70 + 688 + 72 + 0 + 73 + 0 + 74 + 5 + 7 + + 75 + 16 + 76 + 0 + 77 + 2 + 78 + 300 +147 +1.0 +148 +0.0 +149 +0.0 +100 +AcDbLayout + 1 +Layout1 + 70 + 1 + 71 + 1 + 10 +0.0 + 20 +0.0 + 11 +12.0 + 21 +9.0 + 12 +0.0 + 22 +0.0 + 32 +0.0 + 14 +1.000000000000000E+20 + 24 +1.000000000000000E+20 + 34 +1.000000000000000E+20 + 15 +-1.000000000000000E+20 + 25 +-1.000000000000000E+20 + 35 +-1.000000000000000E+20 +146 +0.0 + 13 +0.0 + 23 +0.0 + 33 +0.0 + 16 +1.0 + 26 +0.0 + 36 +0.0 + 17 +0.0 + 27 +1.0 + 37 +0.0 + 76 + 0 +330 +1B + 0 +LAYOUT + 5 +26 +102 +{ACAD_REACTORS +330 +1A +102 +} +330 +1A +100 +AcDbPlotSettings + 1 + + 2 +none_device + 4 + + 6 + + 40 +0.0 + 41 +0.0 + 42 +0.0 + 43 +0.0 + 44 +0.0 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 + 49 +0.0 +140 +0.0 +141 +0.0 +142 +1.0 +143 +1.0 + 70 + 688 + 72 + 0 + 73 + 0 + 74 + 5 + 7 + + 75 + 16 + 76 + 0 + 77 + 2 + 78 + 300 +147 +1.0 +148 +0.0 +149 +0.0 +100 +AcDbLayout + 1 +Layout2 + 70 + 1 + 71 + 2 + 10 +0.0 + 20 +0.0 + 11 +0.0 + 21 +0.0 + 12 +0.0 + 22 +0.0 + 32 +0.0 + 14 +0.0 + 24 +0.0 + 34 +0.0 + 15 +0.0 + 25 +0.0 + 35 +0.0 +146 +0.0 + 13 +0.0 + 23 +0.0 + 33 +0.0 + 16 +1.0 + 26 +0.0 + 36 +0.0 + 17 +0.0 + 27 +1.0 + 37 +0.0 + 76 + 0 +330 +23 + 0 +LAYOUT + 5 +22 +102 +{ACAD_REACTORS +330 +1A +102 +} +330 +1A +100 +AcDbPlotSettings + 1 + + 2 +none_device + 4 + + 6 + + 40 +0.0 + 41 +0.0 + 42 +0.0 + 43 +0.0 + 44 +0.0 + 45 +0.0 + 46 +0.0 + 47 +0.0 + 48 +0.0 + 49 +0.0 +140 +0.0 +141 +0.0 +142 +1.0 +143 +1.0 + 70 + 1712 + 72 + 0 + 73 + 0 + 74 + 0 + 7 + + 75 + 0 + 76 + 0 + 77 + 2 + 78 + 300 +147 +1.0 +148 +0.0 +149 +0.0 +100 +AcDbLayout + 1 +Model + 70 + 1 + 71 + 0 + 10 +0.0 + 20 +0.0 + 11 +12.0 + 21 +9.0 + 12 +0.0 + 22 +0.0 + 32 +0.0 + 14 +1.000000000000000E+20 + 24 +1.000000000000000E+20 + 34 +1.000000000000000E+20 + 15 +-1.000000000000000E+20 + 25 +-1.000000000000000E+20 + 35 +-1.000000000000000E+20 +146 +0.0 + 13 +0.0 + 23 +0.0 + 33 +0.0 + 16 +1.0 + 26 +0.0 + 36 +0.0 + 17 +0.0 + 27 +1.0 + 37 +0.0 + 76 + 0 +330 +1F +331 +29 + 0 +MATERIAL + 5 +3D +102 +{ACAD_REACTORS +330 +3B +102 +} +330 +3B +100 +AcDbMaterial + 1 +ByBlock + 72 + 1 + 94 + 127 + 0 +MATERIAL + 5 +3C +102 +{ACAD_REACTORS +330 +3B +102 +} +330 +3B +100 +AcDbMaterial + 1 +ByLayer + 72 + 1 + 94 + 127 + 0 +MATERIAL + 5 +3E +102 +{ACAD_REACTORS +330 +3B +102 +} +330 +3B +100 +AcDbMaterial + 1 +Global + 72 + 1 + 94 + 127 + 0 +MLINESTYLE + 5 +18 +102 +{ACAD_REACTORS +330 +17 +102 +} +330 +17 +100 +AcDbMlineStyle + 2 +Standard + 70 + 0 + 3 + + 62 + 256 + 51 +90.0 + 52 +90.0 + 71 + 2 + 49 +0.5 + 62 + 256 + 6 +BYLAYER + 49 +-0.5 + 62 + 256 + 6 +BYLAYER + 0 +ACDBPLACEHOLDER + 5 +F +102 +{ACAD_REACTORS +330 +E +102 +} +330 +E + 0 +VISUALSTYLE + 5 +2F +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +2dWireframe + 70 + 4 +177 + 2 +291 + 0 + 71 + 0 +176 + 1 + 72 + 2 +176 + 1 + 73 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 1 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 0 +176 + 1 + 66 + 257 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 1 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +31 +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +3D Hidden + 70 + 6 +177 + 2 +291 + 0 + 71 + 1 +176 + 1 + 72 + 2 +176 + 1 + 73 + 2 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 2 +176 + 1 + 91 + 2 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 2 +176 + 1 +175 + 1 +176 + 1 + 42 +40.0 +176 + 1 + 92 + 0 +176 + 1 + 66 + 257 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 3 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 1 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +30 +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +3dWireframe + 70 + 5 +177 + 2 +291 + 0 + 71 + 0 +176 + 1 + 72 + 2 +176 + 1 + 73 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 1 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 0 +176 + 1 + 66 + 257 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 1 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +32 +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +Basic + 70 + 7 +177 + 2 +291 + 1 + 71 + 1 +176 + 1 + 72 + 0 +176 + 1 + 73 + 1 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 0 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 8 +176 + 1 + 66 + 7 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 1 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +36 +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +Brighten + 70 + 12 +177 + 2 +291 + 1 + 71 + 2 +176 + 1 + 72 + 2 +176 + 1 + 73 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 1 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 8 +176 + 1 + 66 + 7 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 1 +176 + 1 + 44 +50.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +3A +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +ColorChange + 70 + 16 +177 + 2 +291 + 1 + 71 + 2 +176 + 1 + 72 + 2 +176 + 1 + 73 + 3 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 8 +421 + 8421504 +176 + 1 + 74 + 1 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 8 +176 + 1 + 66 + 8 +424 + 8421504 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 1 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +34 +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +Conceptual + 70 + 9 +177 + 2 +291 + 0 + 71 + 3 +176 + 1 + 72 + 2 +176 + 1 + 73 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 2 +176 + 1 + 91 + 2 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +40.0 +176 + 1 + 92 + 8 +176 + 1 + 66 + 7 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 3 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 1 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +35 +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +Dim + 70 + 11 +177 + 2 +291 + 1 + 71 + 2 +176 + 1 + 72 + 2 +176 + 1 + 73 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 1 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 8 +176 + 1 + 66 + 7 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 1 +176 + 1 + 44 +-50.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +39 +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +Facepattern + 70 + 15 +177 + 2 +291 + 1 + 71 + 2 +176 + 1 + 72 + 2 +176 + 1 + 73 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 1 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 8 +176 + 1 + 66 + 7 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 1 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +2B +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +Flat + 70 + 0 +177 + 2 +291 + 1 + 71 + 2 +176 + 1 + 72 + 1 +176 + 1 + 73 + 1 +176 + 1 + 90 + 2 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 0 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 8 +176 + 1 + 66 + 7 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 13 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +2C +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +FlatWithEdges + 70 + 1 +177 + 2 +291 + 1 + 71 + 2 +176 + 1 + 72 + 1 +176 + 1 + 73 + 1 +176 + 1 + 90 + 2 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 1 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 0 +176 + 1 + 66 + 257 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 13 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +2D +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +Gouraud + 70 + 2 +177 + 2 +291 + 1 + 71 + 2 +176 + 1 + 72 + 2 +176 + 1 + 73 + 1 +176 + 1 + 90 + 2 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 0 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 0 +176 + 1 + 66 + 7 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 13 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +2E +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +GouraudWithEdges + 70 + 3 +177 + 2 +291 + 1 + 71 + 2 +176 + 1 + 72 + 2 +176 + 1 + 73 + 1 +176 + 1 + 90 + 2 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 1 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 0 +176 + 1 + 66 + 257 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 13 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +38 +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +Linepattern + 70 + 14 +177 + 2 +291 + 1 + 71 + 2 +176 + 1 + 72 + 2 +176 + 1 + 73 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 1 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 7 +176 + 1 +175 + 7 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 8 +176 + 1 + 66 + 7 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 1 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +33 +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +Realistic + 70 + 8 +177 + 2 +291 + 0 + 71 + 2 +176 + 1 + 72 + 2 +176 + 1 + 73 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 1 +176 + 1 + 91 + 0 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 8 +176 + 1 + 66 + 8 +424 + 7895160 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 13 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +VISUALSTYLE + 5 +37 +102 +{ACAD_REACTORS +330 +2A +102 +} +330 +2A +100 +AcDbVisualStyle + 2 +Thicken + 70 + 13 +177 + 2 +291 + 1 + 71 + 2 +176 + 1 + 72 + 2 +176 + 1 + 73 + 0 +176 + 1 + 90 + 0 +176 + 1 + 40 +-0.6 +176 + 1 + 41 +-30.0 +176 + 1 + 62 + 5 + 63 + 7 +421 + 16777215 +176 + 1 + 74 + 1 +176 + 1 + 91 + 4 +176 + 1 + 64 + 7 +176 + 1 + 65 + 257 +176 + 1 + 75 + 1 +176 + 1 +175 + 1 +176 + 1 + 42 +1.0 +176 + 1 + 92 + 12 +176 + 1 + 66 + 7 +176 + 1 + 43 +1.0 +176 + 1 + 76 + 1 +176 + 1 + 77 + 6 +176 + 1 + 78 + 2 +176 + 1 + 67 + 7 +176 + 1 + 79 + 5 +176 + 1 +170 + 0 +176 + 1 +171 + 0 +176 + 1 +290 + 0 +176 + 1 + 93 + 1 +176 + 1 + 44 +0.0 +176 + 1 +173 + 0 +176 + 1 + 0 +ENDSEC + 0 +EOF diff --git a/Printing/Nameplates/Check-Mark/index.html b/Printing/Nameplates/Check-Mark/index.html new file mode 100644 index 0000000..0a770ad --- /dev/null +++ b/Printing/Nameplates/Check-Mark/index.html @@ -0,0 +1,27 @@ + + + + +Directory listing for /Printing/Nameplates/Check-Mark/ + + +

Directory listing for /Printing/Nameplates/Check-Mark/

+
+ +
+ + diff --git a/Printing/Nameplates/Fordscript.ttf b/Printing/Nameplates/Fordscript.ttf new file mode 100644 index 0000000..ea51e39 Binary files /dev/null and b/Printing/Nameplates/Fordscript.ttf differ diff --git a/Printing/Nameplates/Screenshot 2025-11-07 082805.png b/Printing/Nameplates/Screenshot 2025-11-07 082805.png new file mode 100644 index 0000000..31d17cd Binary files /dev/null and b/Printing/Nameplates/Screenshot 2025-11-07 082805.png differ diff --git a/Printing/Nameplates/add_filament_change_to_3mf.py b/Printing/Nameplates/add_filament_change_to_3mf.py new file mode 100644 index 0000000..ed00962 --- /dev/null +++ b/Printing/Nameplates/add_filament_change_to_3mf.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 +""" +Add M600 filament change command to all objects in an Orca Slicer 3MF file. + +This script modifies a 3MF file (which is a ZIP archive containing XML) to add +a height range modifier at 1.5mm (end of blue base layer) with M600 command +for all zipper pull objects. + +Usage: + python add_filament_change_to_3mf.py input.3mf [output.3mf] + + If output.3mf is not specified, creates input_modified.3mf +""" + +import sys +import zipfile +import xml.etree.ElementTree as ET +from pathlib import Path +import shutil +import tempfile +import os + + +def add_height_range_to_3mf(input_file, output_file=None, height_mm=1.5): + """ + Add height range modifier with M600 at specified height to all objects in 3MF. + + Args: + input_file: Path to input 3MF file + output_file: Path to output 3MF file (optional) + height_mm: Height in mm where filament change occurs (default: 1.5) + """ + input_path = Path(input_file) + + if not input_path.exists(): + print(f"Error: Input file '{input_file}' not found!") + return False + + # Determine output filename + if output_file is None: + output_path = input_path.parent / f"{input_path.stem}_modified.3mf" + else: + output_path = Path(output_file) + + print(f"Processing: {input_path}") + print(f"Output will be: {output_path}") + print(f"Filament change height: {height_mm}mm") + print("-" * 50) + + # Create a temporary directory to work in + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + # Extract the 3MF (it's a ZIP file) + print("Extracting 3MF file...") + with zipfile.ZipFile(input_path, 'r') as zip_ref: + zip_ref.extractall(temp_path) + + # Find and modify the 3D model file (usually 3D/3dmodel.model) + model_file = temp_path / "3D" / "3dmodel.model" + + if not model_file.exists(): + print("Error: Could not find 3dmodel.model in 3MF file!") + return False + + print(f"Modifying {model_file}...") + + # Parse the XML + tree = ET.parse(model_file) + root = tree.getroot() + + # Define namespaces (3MF uses namespaces) + namespaces = { + '': 'http://schemas.microsoft.com/3dmanufacturing/core/2015/02', + 'p': 'http://schemas.microsoft.com/3dmanufacturing/production/2015/06', + 's': 'http://schemas.orca-3d.com/3mf/2023/06' + } + + # Register namespaces for output + for prefix, uri in namespaces.items(): + if prefix: + ET.register_namespace(prefix, uri) + else: + ET.register_namespace('', uri) + + # Find all objects (build items) + # In Orca Slicer 3MF, objects are in tags + build_elem = root.find('.//build', namespaces) + + if build_elem is None: + print("Warning: No element found. Looking for items directly...") + items = root.findall('.//item', namespaces) + else: + items = build_elem.findall('.//item', namespaces) + + if not items: + print("Error: No items found in 3MF file!") + return False + + print(f"Found {len(items)} object(s) in the file") + + # Count modified items + modified_count = 0 + + # Add height range modifier to each item + for idx, item in enumerate(items, 1): + object_id = item.get('objectid', f'unknown_{idx}') + print(f" Processing object {idx}/{len(items)} (ID: {object_id})...") + + # Check if this item already has metadata for height range + # In Orca Slicer, this is typically stored as metadata + # We need to add the height range modifier metadata + + # Note: The exact XML structure for height range modifiers in Orca Slicer + # may vary. This is a generic approach that adds metadata. + # You may need to adjust based on actual Orca Slicer 3MF structure. + + # Create or find metadata container + metadata_group = item.find('metadatagroup', namespaces) + if metadata_group is None: + metadata_group = ET.SubElement(item, 'metadatagroup') + + # Add height range modifier metadata + # Format: height range from 0 to height_mm = blue (original) + # height range from height_mm to top = white (with M600) + + height_modifier = ET.SubElement(metadata_group, 'metadata') + height_modifier.set('name', 'height_range_modifier') + height_modifier.text = f'{{"ranges":[{{"min":0,"max":{height_mm},"color":"RoyalBlue"}},{{"min":{height_mm},"max":999,"color":"white","gcode":"M600"}}]}}' + + modified_count += 1 + + print(f"\nModified {modified_count} object(s)") + + # Save the modified XML back to the file + print("Saving modified model file...") + tree.write(model_file, encoding='utf-8', xml_declaration=True) + + # Re-create the 3MF (ZIP) file with all contents + print("Creating new 3MF file...") + with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as zip_out: + # Walk through the temp directory and add all files + for root_dir, dirs, files in os.walk(temp_path): + for file in files: + file_path = Path(root_dir) / file + arcname = file_path.relative_to(temp_path) + zip_out.write(file_path, arcname) + + print(f"\nSuccess! Modified 3MF saved to: {output_path}") + print(f"\nNext steps:") + print(f"1. Open {output_path.name} in Orca Slicer") + print(f"2. Verify the height range modifiers are present") + print(f"3. Slice and check for M600 commands in the G-code") + return True + + +def main(): + if len(sys.argv) < 2: + print("Usage: python add_filament_change_to_3mf.py input.3mf [output.3mf]") + print("\nAdds M600 filament change at 1.5mm to all objects in a 3MF file.") + print("If output.3mf is not specified, creates input_modified.3mf") + sys.exit(1) + + input_file = sys.argv[1] + output_file = sys.argv[2] if len(sys.argv) > 2 else None + + success = add_height_range_to_3mf(input_file, output_file) + + if not success: + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/Printing/Nameplates/add_height_modifier_orca.py b/Printing/Nameplates/add_height_modifier_orca.py new file mode 100644 index 0000000..5f508ab --- /dev/null +++ b/Printing/Nameplates/add_height_modifier_orca.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +""" +Add M600 filament change to Orca Slicer 3MF file. + +Based on actual Orca Slicer format analysis. +Adds global height_range_modifier metadata that applies to all objects. + +Usage: + python add_height_modifier_orca.py input.3mf [output.3mf] +""" + +import sys +import zipfile +import xml.etree.ElementTree as ET +from pathlib import Path +import tempfile +import os + + +def add_height_modifier(input_file, output_file=None, height_mm=1.5): + """ + Add height range modifier metadata to 3MF file. + + Args: + input_file: Path to input 3MF file + output_file: Path to output 3MF file (optional) + height_mm: Height in mm where filament change occurs + """ + input_path = Path(input_file) + + if not input_path.exists(): + print(f"Error: Input file '{input_file}' not found!") + return False + + if output_file is None: + output_path = input_path.parent / f"{input_path.stem}_modified.3mf" + else: + output_path = Path(output_file) + + print(f"Processing: {input_path}") + print(f"Output: {output_path}") + print(f"Filament change at: {height_mm}mm") + print("-" * 60) + + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + # Extract 3MF + print("Extracting 3MF...") + with zipfile.ZipFile(input_path, 'r') as zip_ref: + zip_ref.extractall(temp_path) + + # Find model file + model_file = temp_path / "3D" / "3dmodel.model" + if not model_file.exists(): + print("Error: Could not find 3dmodel.model!") + return False + + print("Modifying model file...") + + # Parse XML + tree = ET.parse(model_file) + root = tree.getroot() + + # Register namespaces + namespaces = { + '': 'http://schemas.microsoft.com/3dmanufacturing/core/2015/02', + 'BambuStudio': 'http://schemas.bambulab.com/package/2021', + 'p': 'http://schemas.microsoft.com/3dmanufacturing/production/2015/06' + } + + for prefix, uri in namespaces.items(): + if prefix: + ET.register_namespace(prefix, uri) + else: + ET.register_namespace('', uri) + + # Find or create metadata section (should be at root level, before ) + # Check if height_range_modifier already exists + existing_modifier = None + for metadata in root.findall('.//metadata[@name="height_range_modifier"]', namespaces): + existing_modifier = metadata + break + + if existing_modifier is not None: + print(" Found existing height_range_modifier, replacing...") + root.remove(existing_modifier) + + # Create the height range modifier metadata + # Format: JSON string - we'll manually insert it to avoid auto-escaping + metadata_json = ( + '{"ranges":[' + f'{{"min":0,"max":{height_mm},"color":"RoyalBlue"}},' + f'{{"min":{height_mm},"max":999,"color":"white","gcode":"M600"}}' + ']}' + ) + + # Create metadata element - we'll manually set the text with proper escaping + metadata_elem = ET.Element('metadata') + metadata_elem.set('name', 'height_range_modifier') + # Don't use .text = ... as it will auto-escape + # We'll replace this after writing + + # Insert after the last existing metadata element (before ) + resources = root.find('.//resources', namespaces) + if resources is not None: + resources_index = list(root).index(resources) + root.insert(resources_index, metadata_elem) + else: + # No resources found, just append + root.append(metadata_elem) + + print(f" Added height_range_modifier metadata") + print(f" Blue layer: 0mm to {height_mm}mm") + print(f" White layer: {height_mm}mm to top (with M600)") + + # Save modified XML + tree.write(model_file, encoding='utf-8', xml_declaration=True) + + # Post-process the file to fix the escaping + # Python's ET auto-escapes, but we need &quot; not &amp;quot; + with open(model_file, 'r', encoding='utf-8') as f: + content = f.read() + + # Replace the empty metadata element with our properly escaped version + escaped_json = metadata_json.replace('"', '&quot;') + content = content.replace( + '', + f'{escaped_json}' + ) + content = content.replace( + '', + f'{escaped_json}' + ) + + with open(model_file, 'w', encoding='utf-8') as f: + f.write(content) + + # Re-create 3MF file + print("Creating modified 3MF...") + with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as zip_out: + for root_dir, dirs, files in os.walk(temp_path): + for file in files: + file_path = Path(root_dir) / file + arcname = file_path.relative_to(temp_path) + zip_out.write(file_path, arcname) + + print() + print("Success! Modified 3MF saved.") + print() + print("Next steps:") + print("1. Open the modified file in Orca Slicer") + print("2. Check if height range modifier appears") + print("3. Slice and verify M600 in G-code") + print() + print("Note: This adds a GLOBAL height modifier that applies to") + print(" all objects in the build plate.") + + return True + + +def main(): + if len(sys.argv) < 2: + print("Usage: python add_height_modifier_orca.py input.3mf [output.3mf]") + print() + print("Adds M600 filament change at 1.5mm (global, applies to all objects)") + sys.exit(1) + + input_file = sys.argv[1] + output_file = sys.argv[2] if len(sys.argv) > 2 else None + + success = add_height_modifier(input_file, output_file) + + if not success: + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/Printing/Nameplates/checkmark_template.scad b/Printing/Nameplates/checkmark_template.scad new file mode 100644 index 0000000..ed7b143 --- /dev/null +++ b/Printing/Nameplates/checkmark_template.scad @@ -0,0 +1,56 @@ +// Two-Layer Check Mark Design +// Black base (box + check mark) + White fill layer on top +// Based on CF00089-08 Check Mark 01.svg + +// Parameters - adjust these to customize +black_base_thickness = 0.5; // Black base layer (thin, bottom) +white_fill_thickness = 1.5; // White fill layer (middle) +black_top_thickness = 0.5; // Black top (check mark shows through) +desired_size = 14; // Desired box size (mm) - makes design square + +// SVG file path +svg_file = "Check-Mark/CF00089-08 Check Mark 01.svg"; + +// Calculate dimensions from SVG viewBox (720 x 570.9) +svg_width = 720; +svg_height = 570.9; + +// Scale factors to make the design exactly square +scale_x = desired_size / svg_width; // Scale for width +scale_y = desired_size / svg_height; // Scale for height +design_width = desired_size; +design_height = desired_size; + +// White fill inset (how much smaller than the outer box) +white_inset = 1.5; // mm inset from edges (adjusted for 14mm box) +white_width = design_width - (white_inset * 2); +white_height = design_height - (white_inset * 2); + +// Total thickness +total_thickness = black_base_thickness + white_fill_thickness + black_top_thickness; + +echo(str("Design size: ", design_width, "mm x ", design_height, "mm")); +echo(str("White fill size: ", white_width, "mm x ", white_height, "mm")); +echo(str("Total thickness: ", total_thickness, "mm")); + +// Build the two-layer design +module checkmark_design() { + // Layer 1: Black base - full SVG design (box + check mark) + color("black") + translate([0, 0, 0]) + linear_extrude(height = total_thickness) + // Center the SVG design + translate([-design_width/2, -design_height/2, 0]) + scale([scale_x, scale_y, 1]) + import(svg_file, center = false); + + // Layer 2: White fill - sits on top of black base, inside the box + color("white") + translate([0, 0, black_base_thickness]) + linear_extrude(height = white_fill_thickness) + offset(r = 1) // Slightly round the corners + square([white_width, white_height], center = true); +} + +// Render the complete design +checkmark_design(); diff --git a/Printing/Nameplates/fred_bennett_facedown.3mf b/Printing/Nameplates/fred_bennett_facedown.3mf new file mode 100644 index 0000000..d4a642d Binary files /dev/null and b/Printing/Nameplates/fred_bennett_facedown.3mf differ diff --git a/Printing/Nameplates/generate_nameplates.py b/Printing/Nameplates/generate_nameplates.py new file mode 100644 index 0000000..9802506 --- /dev/null +++ b/Printing/Nameplates/generate_nameplates.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +""" +Generate 3D nameplate STL files from a list of names using OpenSCAD. + +This script reads names from a text file and generates individual STL files +for each name using an OpenSCAD template. +""" + +import subprocess +import os +import sys +from pathlib import Path + + +def generate_stl(name, template_file, output_dir): + """ + Generate an STL file for a given name using OpenSCAD. + + Args: + name: The name to put on the nameplate + template_file: Path to the OpenSCAD template file + output_dir: Directory where STL files will be saved + """ + # Create a safe filename from the name (remove special characters) + safe_name = "".join(c for c in name if c.isalnum() or c in (' ', '-', '_')).strip() + safe_name = safe_name.replace(' ', '_') + + output_file = os.path.join(output_dir, f"{safe_name}.stl") + + # Escape quotes in the name for the command line + escaped_name = name.replace('"', '\\"') + + # Build the OpenSCAD command + # -D sets a variable, -o specifies output file + cmd = [ + r'C:\Program Files\OpenSCAD\openscad.exe', + '-D', f'name="{escaped_name}"', + '-o', output_file, + template_file + ] + + print(f"Generating: {safe_name}.stl for '{name}'...") + + try: + # Run OpenSCAD + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True + ) + print(f" [OK] Successfully created {safe_name}.stl") + return True + except subprocess.CalledProcessError as e: + print(f" [ERROR] Error generating {safe_name}.stl") + print(f" {e.stderr}") + return False + except FileNotFoundError: + print("Error: OpenSCAD not found. Please install OpenSCAD and ensure it's in your PATH.") + print("Download from: https://openscad.org/downloads.html") + sys.exit(1) + + +def main(): + # Configuration + template_file = "nameplate_template.scad" + names_file = "names.txt" + output_dir = "output_stl" + + # Check if template exists + if not os.path.exists(template_file): + print(f"Error: Template file '{template_file}' not found!") + sys.exit(1) + + # Check if names file exists + if not os.path.exists(names_file): + print(f"Error: Names file '{names_file}' not found!") + sys.exit(1) + + # Create output directory if it doesn't exist + Path(output_dir).mkdir(exist_ok=True) + + # Read names from file + with open(names_file, 'r', encoding='utf-8') as f: + names = [line.strip() for line in f if line.strip()] + + if not names: + print(f"Error: No names found in '{names_file}'") + sys.exit(1) + + print(f"Found {len(names)} name(s) to process") + print(f"Output directory: {output_dir}") + print("-" * 50) + + # Generate STL for each name + success_count = 0 + for name in names: + if generate_stl(name, template_file, output_dir): + success_count += 1 + + print("-" * 50) + print(f"Complete! Generated {success_count}/{len(names)} STL files") + print(f"Files saved in: {output_dir}/") + + +if __name__ == "__main__": + main() diff --git a/Printing/Nameplates/generate_zipper_pulls.py b/Printing/Nameplates/generate_zipper_pulls.py new file mode 100644 index 0000000..5d84351 --- /dev/null +++ b/Printing/Nameplates/generate_zipper_pulls.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +""" +Generate 3D zipper pull STL files from a list of names using OpenSCAD. + +This script reads names from a text file and generates individual STL files +for each name using the zipper pull OpenSCAD template. +""" + +import subprocess +import os +import sys +from pathlib import Path + + +def generate_stl(name, template_file, output_dir): + """ + Generate an STL file for a given name using OpenSCAD. + + Args: + name: The name to put on the zipper pull + template_file: Path to the OpenSCAD template file + output_dir: Directory where STL files will be saved + """ + # Create a safe filename from the name (remove special characters) + safe_name = "".join(c for c in name if c.isalnum() or c in (' ', '-', '_')).strip() + safe_name = safe_name.replace(' ', '_') + + output_file = os.path.join(output_dir, f"{safe_name}.stl") + + # Escape quotes in the name for the command line + escaped_name = name.replace('"', '\\"') + + # Build the OpenSCAD command + # -D sets a variable, -o specifies output file + cmd = [ + r'C:\Program Files\OpenSCAD\openscad.exe', + '-D', f'name="{escaped_name}"', + '-o', output_file, + template_file + ] + + print(f"Generating: {safe_name}.stl for '{name}'...") + + try: + # Run OpenSCAD + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True + ) + print(f" [OK] Successfully created {safe_name}.stl") + return True + except subprocess.CalledProcessError as e: + print(f" [ERROR] Error generating {safe_name}.stl") + print(f" {e.stderr}") + return False + except FileNotFoundError: + print("Error: OpenSCAD not found. Please install OpenSCAD and ensure it's in your PATH.") + print("Download from: https://openscad.org/downloads.html") + sys.exit(1) + + +def main(): + # Configuration + template_file = "zipper_pull_template.scad" + names_file = "names.txt" + output_dir = "zipper-pulls" + + # Check if template exists + if not os.path.exists(template_file): + print(f"Error: Template file '{template_file}' not found!") + sys.exit(1) + + # Check if names file exists + if not os.path.exists(names_file): + print(f"Error: Names file '{names_file}' not found!") + sys.exit(1) + + # Create output directory if it doesn't exist + Path(output_dir).mkdir(exist_ok=True) + + # Read names from file + with open(names_file, 'r', encoding='utf-8') as f: + names = [line.strip() for line in f if line.strip()] + + if not names: + print(f"Error: No names found in '{names_file}'") + sys.exit(1) + + print(f"Found {len(names)} name(s) to process") + print(f"Output directory: {output_dir}") + print("-" * 50) + + # Generate STL for each name + success_count = 0 + for name in names: + if generate_stl(name, template_file, output_dir): + success_count += 1 + + print("-" * 50) + print(f"Complete! Generated {success_count}/{len(names)} STL files") + print(f"Files saved in: {output_dir}/") + + +if __name__ == "__main__": + main() diff --git a/Printing/Nameplates/generate_zipper_pulls_raised_text.py b/Printing/Nameplates/generate_zipper_pulls_raised_text.py new file mode 100644 index 0000000..8364be5 --- /dev/null +++ b/Printing/Nameplates/generate_zipper_pulls_raised_text.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +""" +Generate 3D zipper pull STL files with raised text from a list of names using OpenSCAD. + +This script reads names from a text file and generates individual STL files +for each name using the raised text zipper pull OpenSCAD template. +""" + +import subprocess +import os +import sys +from pathlib import Path + + +def generate_stl(name, template_file, output_dir): + """ + Generate an STL file for a given name using OpenSCAD. + + Args: + name: The name to put on the zipper pull + template_file: Path to the OpenSCAD template file + output_dir: Directory where STL files will be saved + """ + # Create a safe filename from the name (remove special characters) + safe_name = "".join(c for c in name if c.isalnum() or c in (' ', '-', '_')).strip() + safe_name = safe_name.replace(' ', '_') + + output_file = os.path.join(output_dir, f"{safe_name}.stl") + + # Escape quotes in the name for the command line + escaped_name = name.replace('"', '\\"') + + # Build the OpenSCAD command + # -D sets a variable, -o specifies output file + cmd = [ + r'C:\Program Files\OpenSCAD\openscad.exe', + '-D', f'name="{escaped_name}"', + '-o', output_file, + template_file + ] + + print(f"Generating: {safe_name}.stl for '{name}'...") + + try: + # Run OpenSCAD + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True + ) + print(f" [OK] Successfully created {safe_name}.stl") + return True + except subprocess.CalledProcessError as e: + print(f" [ERROR] Error generating {safe_name}.stl") + print(f" {e.stderr}") + return False + except FileNotFoundError: + print("Error: OpenSCAD not found. Please install OpenSCAD and ensure it's in your PATH.") + print("Download from: https://openscad.org/downloads.html") + sys.exit(1) + + +def main(): + # Configuration + template_file = "zipper_pull_raised_text_template.scad" + names_file = "names.txt" + output_dir = "zipper-pulls-raised-text" + + # Check if template exists + if not os.path.exists(template_file): + print(f"Error: Template file '{template_file}' not found!") + sys.exit(1) + + # Check if names file exists + if not os.path.exists(names_file): + print(f"Error: Names file '{names_file}' not found!") + sys.exit(1) + + # Create output directory if it doesn't exist + Path(output_dir).mkdir(exist_ok=True) + + # Read names from file + with open(names_file, 'r', encoding='utf-8') as f: + names = [line.strip() for line in f if line.strip()] + + if not names: + print(f"Error: No names found in '{names_file}'") + sys.exit(1) + + print(f"Found {len(names)} name(s) to process") + print(f"Output directory: {output_dir}") + print("-" * 50) + + # Generate STL for each name + success_count = 0 + for name in names: + if generate_stl(name, template_file, output_dir): + success_count += 1 + + print("-" * 50) + print(f"Complete! Generated {success_count}/{len(names)} STL files") + print(f"Files saved in: {output_dir}/") + + +if __name__ == "__main__": + main() diff --git a/Printing/Nameplates/generate_zipper_pulls_raised_text_2.py b/Printing/Nameplates/generate_zipper_pulls_raised_text_2.py new file mode 100644 index 0000000..370b0a2 --- /dev/null +++ b/Printing/Nameplates/generate_zipper_pulls_raised_text_2.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +""" +Generate 3D zipper pull STL files with raised text from a list of names using OpenSCAD. + +This script reads names from a text file and generates individual STL files +for each name using the raised text zipper pull OpenSCAD template. + +Version 2: Improved apostrophe handling - preserves apostrophes in both filenames and text rendering. +""" + +import subprocess +import os +import sys +from pathlib import Path + + +def generate_stl(name, template_file, output_dir): + """ + Generate an STL file for a given name using OpenSCAD. + + Args: + name: The name to put on the zipper pull + template_file: Path to the OpenSCAD template file + output_dir: Directory where STL files will be saved + """ + # Create a safe filename from the name + # Keep apostrophes in filename for accurate representation + safe_name = "".join(c for c in name if c.isalnum() or c in (' ', '-', '_', "\'")).strip() + safe_name = safe_name.replace(' ', '_') + + output_file = os.path.join(output_dir, f"{safe_name}.stl") + + # Escape the name for OpenSCAD command line + # Need to escape both quotes and apostrophes properly + escaped_name = name.replace('\\', '\\\\') # Escape backslashes first + escaped_name = escaped_name.replace('"', '\\"') # Escape double quotes + escaped_name = escaped_name.replace("'", "\\'") # Escape single quotes/apostrophes + + # Build the OpenSCAD command + # -D sets a variable, -o specifies output file + cmd = [ + r'C:\Program Files\OpenSCAD\openscad.exe', + '-D', f'name="{escaped_name}"', + '-o', output_file, + template_file + ] + + print(f"Generating: {safe_name}.stl for '{name}'...") + + try: + # Run OpenSCAD + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True + ) + print(f" [OK] Successfully created {safe_name}.stl") + return True + except subprocess.CalledProcessError as e: + print(f" [ERROR] Error generating {safe_name}.stl") + print(f" {e.stderr}") + return False + except FileNotFoundError: + print("Error: OpenSCAD not found. Please install OpenSCAD and ensure it's in your PATH.") + print("Download from: https://openscad.org/downloads.html") + sys.exit(1) + + +def main(): + # Configuration + template_file = "zipper_pull_raised_text_template.scad" + names_file = "names.txt" + output_dir = "zipper-pulls-raised-text-2" + + # Check if template exists + if not os.path.exists(template_file): + print(f"Error: Template file '{template_file}' not found!") + sys.exit(1) + + # Check if names file exists + if not os.path.exists(names_file): + print(f"Error: Names file '{names_file}' not found!") + sys.exit(1) + + # Create output directory if it doesn't exist + Path(output_dir).mkdir(exist_ok=True) + + # Read names from file + with open(names_file, 'r', encoding='utf-8') as f: + names = [line.strip() for line in f if line.strip()] + + if not names: + print(f"Error: No names found in '{names_file}'") + sys.exit(1) + + print(f"Found {len(names)} name(s) to process") + print(f"Output directory: {output_dir}") + print("-" * 50) + + # Generate STL for each name + success_count = 0 + for name in names: + if generate_stl(name, template_file, output_dir): + success_count += 1 + + print("-" * 50) + print(f"Complete! Generated {success_count}/{len(names)} STL files") + print(f"Files saved in: {output_dir}/") + + +if __name__ == "__main__": + main() diff --git a/Printing/Nameplates/index.html b/Printing/Nameplates/index.html new file mode 100644 index 0000000..f25b568 --- /dev/null +++ b/Printing/Nameplates/index.html @@ -0,0 +1,61 @@ + + + + +Directory listing for /Printing/Nameplates/ + + +

Directory listing for /Printing/Nameplates/

+
+ +
+ + diff --git a/Printing/Nameplates/nameplate_template.scad b/Printing/Nameplates/nameplate_template.scad new file mode 100644 index 0000000..0dbebe8 --- /dev/null +++ b/Printing/Nameplates/nameplate_template.scad @@ -0,0 +1,57 @@ +// Two-Layer Oval Nameplate Template with Engraved Text +// Creates a white base oval with a blue top oval and engraved text + +// Parameters - these can be overridden from command line +name = "NAME"; // Text to display on nameplate +oval_width = 100; // Width of the oval (mm) - max 100mm +oval_height = 38; // Height of the oval (mm) - proportional to width +base_thickness = 1.5; // Thickness of white base layer (mm) +top_thickness = 1; // Thickness of blue top layer (mm) +base_offset = 2; // How much larger the white base is (mm) +text_size = 15; // Font size for the text +text_depth = 1; // How deep the text is engraved (mm) - cuts through blue to show white +font_file = "C:/Users/Fred/claude/Fordscript.ttf"; // Path to the custom font file + +// Module to create an oval (ellipse) +module oval(width, height, depth) { + scale([width/2, height/2, 1]) + cylinder(h=depth, r=1, $fn=100); +} + +// White base layer (larger oval) +module base_layer() { + color("white") + oval( + oval_width + base_offset*2, + oval_height + base_offset*2, + base_thickness + ); +} + +// Blue top layer with engraved text +module top_layer() { + color("RoyalBlue") + difference() { + // Blue oval + translate([0, 0, base_thickness]) + oval(oval_width, oval_height, top_thickness); + + // Engraved text (cuts into the blue layer) + translate([0, 0, base_thickness + top_thickness - text_depth + 0.01]) + linear_extrude(height=text_depth) + text(name, + size=text_size, + font="Fordscript", + halign="center", + valign="center"); + } +} + +// Main nameplate assembly +module nameplate() { + base_layer(); + top_layer(); +} + +// Generate the nameplate +nameplate(); diff --git a/Printing/Nameplates/names.txt b/Printing/Nameplates/names.txt new file mode 100644 index 0000000..e7905de --- /dev/null +++ b/Printing/Nameplates/names.txt @@ -0,0 +1,58 @@ +Skylar +Aaron +Kathrynn +Alice +Jaxxin +Sydney +Blake +Ayden +Bentley +Landon +Silas +Aniah +Silas +Donald +Hunter +Harper +Keith +Karleigh +Michael +Taylor +Adalynn +Harley +Danny +Carson +Aur'heir +Kinleigh +Bennett +Lucy +Grayson +Leon +Charlotte +Landon +Aiden +Nevaeh +Ayla +Leah +William +Lane +Liberty +Karter +Wyatt +Clare +Bryant +Cole +Jordan +Max +Zoey +Eli +Oliver +Jack +Tyler +Annabella +Amaya +Shane +Nicholas +Clay +Jayden +Alexis \ No newline at end of file diff --git a/Printing/Nameplates/output_stl/index.html b/Printing/Nameplates/output_stl/index.html new file mode 100644 index 0000000..2f0579d --- /dev/null +++ b/Printing/Nameplates/output_stl/index.html @@ -0,0 +1,15 @@ + + + + +Directory listing for /Printing/Nameplates/output_stl/ + + +

Directory listing for /Printing/Nameplates/output_stl/

+
+ +
+ + diff --git a/Printing/Nameplates/test_bennett_facedown.3mf b/Printing/Nameplates/test_bennett_facedown.3mf new file mode 100644 index 0000000..c4367e4 Binary files /dev/null and b/Printing/Nameplates/test_bennett_facedown.3mf differ diff --git a/Printing/Nameplates/test_blue_only.3mf b/Printing/Nameplates/test_blue_only.3mf new file mode 100644 index 0000000..d1dc16c Binary files /dev/null and b/Printing/Nameplates/test_blue_only.3mf differ diff --git a/Printing/Nameplates/test_blue_only.scad b/Printing/Nameplates/test_blue_only.scad new file mode 100644 index 0000000..caa0088 --- /dev/null +++ b/Printing/Nameplates/test_blue_only.scad @@ -0,0 +1,61 @@ +// Test - only blue layer from main template +name = "Bennett"; +oval_width = 96; +oval_height = 30; +blue_thickness = 1; +white_thickness = 1.5; +base_text_size = 13; + +name_length = len(name); +text_size = name_length <= 3 ? 18 : + name_length <= 5 ? 13 : + name_length <= 8 ? 11 : + 11; + +border_width = 2; +hole_diameter = 4; +hole_clearance = 4; + +total_width = oval_width + border_width*2; +total_height = oval_height + border_width*2; +hole_x = -(total_width/2) + (hole_diameter/2) + hole_clearance; + +module oval(width, height, depth) { + scale([width/2, height/2, 1]) + cylinder(h=depth, r=1, $fn=100); +} + +module bold_text() { + for (x = [-0.3, 0, 0.3]) { + for (y = [-0.3, 0, 0.3]) { + translate([x, y, 0]) + linear_extrude(height=blue_thickness + 0.1) + text(name, + size=text_size, + font="Fordscript", + halign="center", + valign="center"); + } + } +} + +// Blue layer only +color("RoyalBlue") +difference() { + oval(total_width, total_height, blue_thickness); + + // Cut out the border ring channel + translate([0, 0, -0.05]) + difference() { + oval(total_width, total_height, blue_thickness + 0.1); + oval(oval_width, oval_height, blue_thickness + 0.2); + } + + // Cut out text channels + translate([0, 0, -0.05]) + bold_text(); + + // Cut out hole + translate([hole_x, 0, -0.05]) + cylinder(h=blue_thickness + 0.1, d=hole_diameter, $fn=50); +} diff --git a/Printing/Nameplates/test_debug.3mf b/Printing/Nameplates/test_debug.3mf new file mode 100644 index 0000000..98e37ea Binary files /dev/null and b/Printing/Nameplates/test_debug.3mf differ diff --git a/Printing/Nameplates/test_debug.scad b/Printing/Nameplates/test_debug.scad new file mode 100644 index 0000000..7cdd247 --- /dev/null +++ b/Printing/Nameplates/test_debug.scad @@ -0,0 +1,43 @@ +// Debug - just show the blue layer with text cut out +name = "Bennett"; +oval_width = 96; +oval_height = 30; +blue_thickness = 1; +border_width = 2; + +name_length = len(name); +text_size = name_length <= 3 ? 18 : + name_length <= 5 ? 13 : + name_length <= 8 ? 11 : + 11; + +total_width = oval_width + border_width*2; +total_height = oval_height + border_width*2; + +module oval(width, height, depth) { + scale([width/2, height/2, 1]) + cylinder(h=depth, r=1, $fn=100); +} + +module bold_text() { + for (x = [-0.3, 0, 0.3]) { + for (y = [-0.3, 0, 0.3]) { + translate([x, y, 0]) + linear_extrude(height=blue_thickness + 0.1) + text(name, + size=text_size, + font="Fordscript", + halign="center", + valign="center"); + } + } +} + +// Just the blue with text cut - no border cut +color("RoyalBlue") +difference() { + oval(total_width, total_height, blue_thickness); + + translate([0, 0, -0.05]) + bold_text(); +} diff --git a/Printing/Nameplates/test_font.scad b/Printing/Nameplates/test_font.scad new file mode 100644 index 0000000..628caad --- /dev/null +++ b/Printing/Nameplates/test_font.scad @@ -0,0 +1,3 @@ +// Test if Fordscript font renders +linear_extrude(height=2) + text("Bennett", size=11, font="Fordscript", halign="center", valign="center"); diff --git a/Printing/Nameplates/test_simple_fred.scad b/Printing/Nameplates/test_simple_fred.scad new file mode 100644 index 0000000..93d33a1 --- /dev/null +++ b/Printing/Nameplates/test_simple_fred.scad @@ -0,0 +1,12 @@ +// Simple test for Fred text rendering +name = "Fred"; +text_size = 13; + +color("white") +translate([0, 0, 1.5]) + linear_extrude(height=1) + text(name, + size=text_size, + font="Fordscript", + halign="center", + valign="center"); diff --git a/Printing/Nameplates/zipper-pulls-raised-text-2/index.html b/Printing/Nameplates/zipper-pulls-raised-text-2/index.html new file mode 100644 index 0000000..cc3b0f2 --- /dev/null +++ b/Printing/Nameplates/zipper-pulls-raised-text-2/index.html @@ -0,0 +1,70 @@ + + + + +Directory listing for /Printing/Nameplates/zipper-pulls-raised-text-2/ + + +

Directory listing for /Printing/Nameplates/zipper-pulls-raised-text-2/

+
+ +
+ + diff --git a/Printing/Nameplates/zipper-pulls-raised-text/index.html b/Printing/Nameplates/zipper-pulls-raised-text/index.html new file mode 100644 index 0000000..02c30f5 --- /dev/null +++ b/Printing/Nameplates/zipper-pulls-raised-text/index.html @@ -0,0 +1,82 @@ + + + + +Directory listing for /Printing/Nameplates/zipper-pulls-raised-text/ + + +

Directory listing for /Printing/Nameplates/zipper-pulls-raised-text/

+
+ +
+ + diff --git a/Printing/Nameplates/zipper_pull_raised_text_template.scad b/Printing/Nameplates/zipper_pull_raised_text_template.scad new file mode 100644 index 0000000..6d48368 --- /dev/null +++ b/Printing/Nameplates/zipper_pull_raised_text_template.scad @@ -0,0 +1,117 @@ +// Face-Down Oval Zipper Pull Template +// Designed for face-down printing: text and border are channels in blue layer (printed first), +// then white backing fills the channels and covers the back. +// When flipped over, shows white text/border on blue background. + +// Parameters - these can be overridden from command line +name = "NAME"; // Text to display on zipper pull +oval_width = 96; // Width of blue oval (mm) - total with border will be 100mm (96 + 2*2) +oval_height = 30; // Height of blue oval (mm) - total with border will be 34mm (30 + 2*2) +blue_thickness = 1; // Thickness of blue layer (mm) - this is the "show" side +white_thickness = 1.5; // Thickness of white backing layer (mm) +base_text_size = 13; // Base font size for medium names + +// Dynamic font sizing based on name length (mimics Ford oval proportions) +// "Fred" (4 chars) at 13mm gives ~20% coverage - our target +name_length = len(name); +text_size = name_length <= 3 ? 18 : // Short names (Zoe, Sam, Al) - larger (~21% coverage) + name_length <= 5 ? 13 : // Medium names (Fred, John, Mary) - standard (~20% coverage) + name_length <= 8 ? 11 : // Longer names (Michael, Jessica) - smaller (~25% coverage) + 11; // Very long names (Christopher) - increased to 11mm (~35% coverage) + +// Border parameters +border_width = 2; // Width of the white border around the oval (mm) + +// Hole parameters for zipper pull +hole_diameter = 4; // Diameter of the hole (mm) +hole_clearance = 4; // Minimum clearance from edge of outer border (mm) + +// Total dimensions +total_width = oval_width + border_width*2; // 100mm +total_height = oval_height + border_width*2; // 34mm + +// Hole position - on LEFT side when viewed from show side +// Since we print face-down, hole appears on RIGHT during printing +// When flipped, it will be on the left as expected +hole_x = -(total_width/2) + (hole_diameter/2) + hole_clearance; + +// Module to create an oval (ellipse) +module oval(width, height, depth) { + scale([width/2, height/2, 1]) + cylinder(h=depth, r=1, $fn=100); +} + +// Module for text with fake bold effect +module bold_text() { + for (x = [-0.3, 0, 0.3]) { + for (y = [-0.3, 0, 0.3]) { + translate([x, y, 0]) + linear_extrude(height=blue_thickness + 0.1) + text(name, + size=text_size, + font="Fordscript", + halign="center", + valign="center"); + } + } +} + +// Blue layer with channels for text and border (prints first, face-down) +// The channels will be filled with white when the white layer is printed +module blue_layer() { + color("RoyalBlue") + difference() { + // Full blue oval + oval(total_width, total_height, blue_thickness); + + // Cut out the border ring channel (will be filled with white) + translate([0, 0, -0.05]) + difference() { + oval(total_width, total_height, blue_thickness + 0.1); + oval(oval_width, oval_height, blue_thickness + 0.2); + } + + // Cut out text channels (will be filled with white) + translate([0, 0, -0.05]) + bold_text(); + + // Cut out hole + translate([hole_x, 0, -0.05]) + cylinder(h=blue_thickness + 0.1, d=hole_diameter, $fn=50); + } +} + +// White backing layer (prints second, on top of blue) +// Also fills the text and border channels +module white_layer() { + color("white") + difference() { + union() { + // Main backing layer on top of blue + translate([0, 0, blue_thickness]) + oval(total_width, total_height, white_thickness); + + // Fill the border channel + difference() { + oval(total_width, total_height, blue_thickness); + oval(oval_width, oval_height, blue_thickness + 0.1); + } + + // Fill the text channels + bold_text(); + } + + // Cut out hole through entire white layer + translate([hole_x, 0, -0.05]) + cylinder(h=blue_thickness + white_thickness + 0.2, d=hole_diameter, $fn=50); + } +} + +// Main zipper pull assembly +module zipper_pull() { + blue_layer(); + white_layer(); +} + +// Generate the zipper pull +zipper_pull(); diff --git a/Printing/Nameplates/zipper_pull_raised_text_template_REDUCED_BOLD.scad b/Printing/Nameplates/zipper_pull_raised_text_template_REDUCED_BOLD.scad new file mode 100644 index 0000000..9040739 --- /dev/null +++ b/Printing/Nameplates/zipper_pull_raised_text_template_REDUCED_BOLD.scad @@ -0,0 +1,88 @@ +// Single-Layer Oval Zipper Pull Template with Raised Text +// Creates a blue base oval with raised white text and a hole for zipper attachment + +// Parameters - these can be overridden from command line +name = "NAME"; // Text to display on zipper pull +oval_width = 96; // Width of blue oval (mm) - total with border will be 100mm (96 + 2*2) +oval_height = 30; // Height of blue oval (mm) - total with border will be 34mm (30 + 2*2) +base_thickness = 1.5; // Thickness of blue base layer (mm) +text_thickness = 1; // Thickness of raised white text (mm) +base_text_size = 13; // Base font size for medium names +font_file = "C:/Users/Fred/claude/Fordscript.ttf"; // Path to the custom font file + +// Dynamic font sizing based on name length (mimics Ford oval proportions) +// "Fred" (4 chars) at 13mm gives ~20% coverage - our target +name_length = len(name); +text_size = name_length <= 3 ? 18 : // Short names (Zoe, Sam, Al) - larger + name_length <= 5 ? 13 : // Medium names (Fred, John, Mary) - standard + name_length <= 8 ? 11 : // Longer names (Michael, Jessica) - smaller + 9; // Very long names (Christopher) - smallest + +// Border parameters +border_width = 2; // Width of the white border around the oval (mm) +border_thickness = 1; // Thickness of the white border (mm) + +// Hole parameters for zipper pull +hole_diameter = 4; // Diameter of the hole (mm) +hole_clearance = 3; // Minimum clearance from edge of outer border (mm) - increased to fully clear white border + +// Module to create an oval (ellipse) +module oval(width, height, depth) { + scale([width/2, height/2, 1]) + cylinder(h=depth, r=1, $fn=100); +} + +// Blue base layer with hole +module base_layer() { + color("RoyalBlue") + difference() { + // Blue base matches the outer size of the white border + oval(oval_width + border_width*2, oval_height + border_width*2, base_thickness); + + // Hole for zipper pull - positioned relative to outer edge + // Total width = oval_width + border_width*2 + // Position: left edge + hole_radius + clearance + total_width = oval_width + border_width*2; + hole_x = -(total_width/2) + (hole_diameter/2) + hole_clearance; + translate([hole_x, 0, -0.05]) + cylinder(h=base_thickness + 0.1, d=hole_diameter, $fn=50); + } +} + +// White border around the oval +module white_border() { + color("white") + translate([0, 0, base_thickness]) + difference() { + // Outer oval (larger) + oval(oval_width + border_width*2, oval_height + border_width*2, border_thickness); + // Inner oval (cut out the center) + translate([0, 0, -0.05]) + oval(oval_width, oval_height, border_thickness + 0.1); + } +} + +// Raised white text on top of base - REDUCED bold effect (3 renders instead of 9) +module raised_text() { + color("white") + // Reduced bold effect - only 3 offsets + for (x = [-0.4, 0, 0.4]) { + translate([x, 0, base_thickness]) + linear_extrude(height=text_thickness) + text(name, + size=text_size, + font="Fordscript", + halign="center", + valign="center"); + } +} + +// Main zipper pull assembly +module zipper_pull() { + base_layer(); + white_border(); + raised_text(); +} + +// Generate the zipper pull +zipper_pull(); diff --git a/Printing/Nameplates/zipper_pull_template.scad b/Printing/Nameplates/zipper_pull_template.scad new file mode 100644 index 0000000..0b25a86 --- /dev/null +++ b/Printing/Nameplates/zipper_pull_template.scad @@ -0,0 +1,90 @@ +// Two-Layer Oval Zipper Pull Template with Engraved Text +// Creates a white base oval with a blue top oval, engraved text, and a hole for zipper attachment + +// Parameters - these can be overridden from command line +name = "NAME"; // Text to display on zipper pull +oval_width = 96; // Width of blue oval (mm) - white base will be 100mm total (96 + 2*2) +oval_height = 30; // Height of blue oval (mm) - white base will be 34mm total (30 + 2*2) +base_thickness = 1.5; // Thickness of white base layer (mm) +top_thickness = 1; // Thickness of blue top layer (mm) +base_offset = 2; // How much larger the white base is (mm) +base_text_size = 13; // Base font size for medium names (reduced to prevent interference with hole) +text_depth = 1; // How deep the text is engraved (mm) - cuts through blue to show white +font_file = "C:/Users/Fred/claude/Fordscript.ttf"; // Path to the custom font file + +// Dynamic font sizing based on name length (mimics Ford oval proportions) +// "Fred" (4 chars) at 13mm gives ~20% coverage - our target +name_length = len(name); +text_size = name_length <= 3 ? 18 : // Short names (Zoe, Sam, Al) - larger + name_length <= 5 ? 13 : // Medium names (Fred, John, Mary) - standard + name_length <= 8 ? 11 : // Longer names (Michael, Jessica) - smaller + 9; // Very long names (Christopher) - smallest + +// Hole parameters for zipper pull +hole_diameter = 4; // Diameter of the hole (mm) +hole_clearance = 1; // Minimum clearance from edge of white base layer (mm) + +// Module to create an oval (ellipse) +module oval(width, height, depth) { + scale([width/2, height/2, 1]) + cylinder(h=depth, r=1, $fn=100); +} + +// White base layer (larger oval) with hole +module base_layer() { + color("white") + difference() { + oval( + oval_width + base_offset*2, + oval_height + base_offset*2, + base_thickness + ); + + // Hole for zipper pull - positioned relative to white base edge + // Calculate from white base width: (oval_width + base_offset*2) + white_width = oval_width + base_offset*2; + hole_x = -(white_width/2) + (hole_diameter/2) + hole_clearance; + translate([hole_x, 0, -0.05]) + cylinder(h=base_thickness + 0.1, d=hole_diameter, $fn=50); + } +} + +// Blue top layer with engraved text and hole +module top_layer() { + color("RoyalBlue") + difference() { + // Blue oval + translate([0, 0, base_thickness]) + oval(oval_width, oval_height, top_thickness); + + // Engraved text (cuts into the blue layer) + // Multiple offset renders create a "fake bold" effect + for (x = [-0.3, 0, 0.3]) { + for (y = [-0.3, 0, 0.3]) { + translate([x, y, base_thickness + top_thickness - text_depth + 0.01]) + linear_extrude(height=text_depth) + text(name, + size=text_size, + font="Fordscript", + halign="center", + valign="center"); + } + } + + // Hole for zipper pull (positioned on left side) + // Position: matches white base layer hole position + white_width = oval_width + base_offset*2; + hole_x = -(white_width/2) + (hole_diameter/2) + hole_clearance; + translate([hole_x, 0, base_thickness]) + cylinder(h=top_thickness + 0.1, d=hole_diameter, $fn=50); + } +} + +// Main zipper pull assembly +module zipper_pull() { + base_layer(); + top_layer(); +} + +// Generate the zipper pull +zipper_pull(); diff --git a/Printing/index.html b/Printing/index.html new file mode 100644 index 0000000..3919cf7 --- /dev/null +++ b/Printing/index.html @@ -0,0 +1,20 @@ + + + + +Directory listing for /Printing/ + + +

Directory listing for /Printing/

+
+ +
+ + diff --git a/README.md b/README.md new file mode 100644 index 0000000..53c9e93 --- /dev/null +++ b/README.md @@ -0,0 +1,265 @@ +# Fred's Projects - Source of Truth + +This is the master directory for all active projects. It serves as the "source of truth" for Claude Code sessions. + +--- + +## Quick Start with VS Code Insiders + +**Open the workspace**: +``` +Double-click: fred-workspace.code-workspace +``` + +Or from VS Code: `File โ†’ Open Workspace โ†’ fred-workspace.code-workspace` + +**Start Claude Code**: +```bash +cd C:\Users\Fred\projects +claude +``` + +**Tell Claude to load context**: +``` +"Read .claude-context.md to understand my project structure" +``` + +--- + +## Projects Overview + +### ๐ŸŽฏ [claude-workflows](claude-workflows/) +**ADHD-friendly productivity tools for Claude Code** +- Slash commands (`/push`, `/eod`) +- ADHD assistant with sidequest detection +- Auto-discovery for cross-project setup + +**Start here when**: Working on Claude Code tooling, productivity features + +--- + +### ๐Ÿฅ [VA-Strategy](VA-Strategy/) +**VA disability claims management system** +- Goal: 100% VA rating via TDIU +- Current: 60% combined (30% highest single) +- Tracking, evidence, statements, forms + +**Start here when**: Working on VA claims, medical documentation + +**Quick commands**: +```bash +cd VA-Strategy +git status # Check what's changed +cat tracking/master-tracking.md # See current status +``` + +--- + +### ๐Ÿ  [infrastructure](infrastructure/) +**Home network, Home Assistant, smart home** +- Home Assistant configuration +- ESPHome devices (garage controller, furnace) +- Voice assistant system (GPU-accelerated, local) +- Network infrastructure (MQTT, DNS-over-TLS) + +**Start here when**: Working on home automation, voice assistant, ESPHome + +**Active subprojects**: +- Voice Assistant: Gaming PC + Surface Go +- Furnace Control: ESP32 planning phase +- Home Assistant: Main config + +--- + +### โš™๏ธ [config](config/) +**Shared configuration files** + +Minimal/placeholder for cross-project configs. + +--- + +### ๐Ÿ“š [claude-code-history](claude-code-history/) +**Background: Claude Code session history** + +Session transcripts, state files, stats. Mostly hidden from searches. + +--- + +## Key Files + +| File | Purpose | +|------|---------| +| `.claude-context.md` | Master context file - tells Claude about all projects | +| `fred-workspace.code-workspace` | VS Code multi-root workspace | +| `VSCODE-SETUP.md` | Detailed setup guide for VS Code + Claude | +| `README.md` | This file - quick reference | + +--- + +## ADHD-Friendly Workflow + +### How Sidequest Detection Works + +1. You're working in one project (e.g., VA-Strategy) +2. You start exploring something related to another project (e.g., ESP32 for infrastructure) +3. Claude detects the context shift +4. Claude offers to: + - Track it as a side quest + - Switch projects formally + - Create a new project + - Return to original work + +### Example + +``` +You: [Working in VA-Strategy on headache log] +You: "I wonder if I could automate headache tracking with Home Assistant" + +Claude: ๐Ÿค” Side quest detected! + + Current: VA-Strategy (headache log) + New idea: HA automation (infrastructure) + + Options: + 1. Continue exploring (I'll track it) + 2. Switch to infrastructure project + 3. Create new "health-automation" project + 4. Return to headache log +``` + +--- + +## Common Workflows + +### Start Working on a Project +```bash +cd C:\Users\Fred\projects\VA-Strategy +claude +# Tell Claude what you want to work on +``` + +### Switch Projects Mid-Session +Just tell Claude: +``` +"I want to switch to working on infrastructure now" +``` + +Claude will track the context switch. + +### Explore a Side Quest +``` +"This is a side quest - I want to explore X for 20 minutes" +``` + +Claude will set a timer and check in. + +### End of Day +``` +/eod +``` + +Claude will: +- Commit your changes +- Show what you accomplished +- Prepare for tomorrow + +--- + +## Setup Checklist + +- [x] `.claude-context.md` created +- [x] Workspace file created +- [ ] Open workspace in VS Code Insiders +- [ ] Create ADHD assistant state directory: + ```powershell + New-Item -ItemType Directory -Path "$env:USERPROFILE\.claude-assistant" -Force + Copy-Item "claude-workflows\.assistant\state.json.template" "$env:USERPROFILE\.claude-assistant\state.json" + ``` +- [ ] Start Claude Code session +- [ ] Test sidequest detection + +--- + +## Files You Should Know About + +### Global Context +- **`.claude-context.md`** - Tells Claude about all your projects +- **`fred-workspace.code-workspace`** - Multi-root workspace for VS Code +- **`VSCODE-SETUP.md`** - Detailed setup instructions + +### ADHD Assistant +- **`claude-workflows/.assistant/personality.md`** - How Claude should behave +- **`claude-workflows/.assistant/state.json.template`** - Session state template +- **`~/.claude-assistant/state.json`** - Your active state file (to be created) + +### Project-Specific +- **`VA-Strategy/CLAUDE.md`** - VA project context +- **`VA-Strategy/README.md`** - VA project overview +- **`infrastructure/README.md`** - Infrastructure overview +- **`claude-workflows/README.md`** - Workflows overview + +--- + +## Customization + +### Adjust ADHD Assistant Behavior + +Edit: `~/.claude-assistant/state.json` + +```json +{ + "user": { + "preferences": { + "intervention_style": "gentle", // gentle | assertive | minimal + "stuck_threshold": 3, // How many times before intervention + "sidequest_time_limit_minutes": 30, // Check-in time + "celebrates_completions": true // Celebrate wins + } + } +} +``` + +### Add More Projects + +Edit: `fred-workspace.code-workspace` + +Add new folder: +```json +{ + "path": "new-project", + "name": "๐Ÿ“ฆ New Project" +} +``` + +--- + +## Getting Help + +### Claude Code +- `/help` - Claude Code help +- Ask Claude: "How does sidequest detection work?" + +### Project-Specific +- Each project has a README.md +- VA-Strategy and infrastructure have CLAUDE.md files + +### Issues +Report at: https://github.com/anthropics/claude-code/issues + +--- + +## Philosophy + +This setup is designed to work **with** ADHD, not against it: + +โœ“ Side quests are valid exploration +โœ“ Context switching is supported +โœ“ Progress is celebrated +โœ“ No judgment on workflow +โœ“ Gentle nudging, not rigid control + +Claude is here to help you stay aware of what you're working on, not to police your focus. + +--- + +**Ready?** Open `fred-workspace.code-workspace` and start a Claude session! diff --git a/Scaled M'Cheyne Bible Reading Plan.xlsx b/Scaled M'Cheyne Bible Reading Plan.xlsx new file mode 100644 index 0000000..64d4cab Binary files /dev/null and b/Scaled M'Cheyne Bible Reading Plan.xlsx differ diff --git a/Screenshot 2025-12-29 220821.png b/Screenshot 2025-12-29 220821.png new file mode 100644 index 0000000..e709f92 Binary files /dev/null and b/Screenshot 2025-12-29 220821.png differ diff --git a/Screenshot 2025-12-29 224439.png b/Screenshot 2025-12-29 224439.png new file mode 100644 index 0000000..06c875e Binary files /dev/null and b/Screenshot 2025-12-29 224439.png differ diff --git a/Screenshot 2025-12-30 212901.png b/Screenshot 2025-12-30 212901.png new file mode 100644 index 0000000..5a7c0b7 Binary files /dev/null and b/Screenshot 2025-12-30 212901.png differ diff --git a/Screenshot 2025-12-30 213242.png b/Screenshot 2025-12-30 213242.png new file mode 100644 index 0000000..ed0109a Binary files /dev/null and b/Screenshot 2025-12-30 213242.png differ diff --git a/Screenshot 2025-12-31 001705.png b/Screenshot 2025-12-31 001705.png new file mode 100644 index 0000000..d83cfc4 Binary files /dev/null and b/Screenshot 2025-12-31 001705.png differ diff --git a/Screenshot 2025-12-31 001834.png b/Screenshot 2025-12-31 001834.png new file mode 100644 index 0000000..440ee1c Binary files /dev/null and b/Screenshot 2025-12-31 001834.png differ diff --git a/Screenshot 2025-12-31 001846.png b/Screenshot 2025-12-31 001846.png new file mode 100644 index 0000000..220378d Binary files /dev/null and b/Screenshot 2025-12-31 001846.png differ diff --git a/Screenshot 2025-12-31 133905.png b/Screenshot 2025-12-31 133905.png new file mode 100644 index 0000000..d5688fc Binary files /dev/null and b/Screenshot 2025-12-31 133905.png differ diff --git a/Screenshot 2026-01-01 201236.png b/Screenshot 2026-01-01 201236.png new file mode 100644 index 0000000..55d9faf Binary files /dev/null and b/Screenshot 2026-01-01 201236.png differ diff --git a/Screenshot 2026-01-01 203543.png b/Screenshot 2026-01-01 203543.png new file mode 100644 index 0000000..4b5c7da Binary files /dev/null and b/Screenshot 2026-01-01 203543.png differ diff --git a/Screenshot 2026-01-09 102341.png b/Screenshot 2026-01-09 102341.png new file mode 100644 index 0000000..bff4f0c Binary files /dev/null and b/Screenshot 2026-01-09 102341.png differ diff --git a/add-acme-provisioner.py b/add-acme-provisioner.py new file mode 100644 index 0000000..29da3c4 --- /dev/null +++ b/add-acme-provisioner.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +import json + +# Read the config +with open('C:/Users/Fred/AppData/Local/Temp/ca.json', 'r') as f: + config = json.load(f) + +# Add ACME provisioner +acme_provisioner = { + "type": "ACME", + "name": "acme", + "forceCN": True, + "claims": { + "maxTLSCertDuration": "8760h", + "defaultTLSCertDuration": "8760h" + } +} + +# Add to provisioners list +config['authority']['provisioners'].append(acme_provisioner) + +# Write updated config +with open('C:/Users/Fred/AppData/Local/Temp/ca.json', 'w') as f: + json.dump(config, f, indent=4) + +print("ACME provisioner added successfully") diff --git a/add-cocktails-to-caddy.sh b/add-cocktails-to-caddy.sh new file mode 100644 index 0000000..76f84bf --- /dev/null +++ b/add-cocktails-to-caddy.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# Add Bar Assistant (cocktails.nianticbooks.com) to Caddy + +echo "Adding cocktails.nianticbooks.com to Caddy..." + +sudo tee -a /etc/caddy/Caddyfile > /dev/null << 'EOF' + +# Bar Assistant - Cocktail Recipe Manager +cocktails.nianticbooks.com { + reverse_proxy http://10.0.10.40:80 { + header_up X-Forwarded-For {remote_host} + header_up X-Real-IP {remote_host} + } +} +EOF + +echo "Configuration added!" +echo "" +echo "Reloading Caddy..." +sudo systemctl reload caddy + +echo "" +echo "Testing configuration..." +sleep 3 +curl -I https://cocktails.nianticbooks.com 2>&1 | head -5 + +echo "" +echo "โœ… Done! Bar Assistant should now be available at:" +echo " https://cocktails.nianticbooks.com" diff --git a/aider-helpers.ps1 b/aider-helpers.ps1 new file mode 100644 index 0000000..cc55901 --- /dev/null +++ b/aider-helpers.ps1 @@ -0,0 +1,71 @@ +# Aider Helper Functions for Fred's Workflow +# Add to PowerShell profile: . C:\Users\Fred\projects\aider-helpers.ps1 + +# Quick launch Aider with 7b model (fast, everyday coding) +function aider-fast { + aider --model ollama/qwen2.5-coder:7b-instruct @args +} + +# Launch Aider with 14b model (complex tasks, better reasoning) +function aider-smart { + aider --model ollama/qwen2.5-coder:14b-instruct-q4_K_M @args +} + +# Launch Aider with architect mode (planning, design) +function aider-plan { + aider --model ollama/qwen2.5-coder:14b-instruct-q4_K_M --architect @args +} + +# Quick commit with Aider (use for git commit messages) +function aider-commit { + aider --model ollama/qwen2.5-coder:7b-instruct --commit +} + +# Launch Aider in watch mode (auto-reload on file changes) +function aider-watch { + aider --model ollama/qwen2.5-coder:7b-instruct --watch-files @args +} + +# Show Aider status and current models +function aider-status { + Write-Host "=== Aider Status ===" -ForegroundColor Cyan + Write-Host "" + Write-Host "Available models:" -ForegroundColor Yellow + ollama list | Select-String "qwen2.5-coder" + Write-Host "" + Write-Host "Quick commands:" -ForegroundColor Yellow + Write-Host " aider-fast - Fast 7b model (everyday coding)" + Write-Host " aider-smart - Powerful 14b model (complex tasks)" + Write-Host " aider-plan - Architect mode (planning)" + Write-Host " aider-commit - Generate commit messages" + Write-Host " aider-watch - Watch mode (auto-reload)" + Write-Host "" + Write-Host "Example usage:" -ForegroundColor Yellow + Write-Host " cd C:\Users\Fred\projects\VA-Strategy" + Write-Host " aider-fast" + Write-Host " > Add a function to parse headache log entries" +} + +# Token usage estimator +function aider-estimate { + param( + [Parameter(Mandatory=$false)] + [string]$model = "7b" + ) + + Write-Host "=== Token Cost Comparison ===" -ForegroundColor Cyan + Write-Host "" + Write-Host "Local Ollama (your setup):" -ForegroundColor Green + Write-Host " Cost: `$0.00 (free!)" + Write-Host " Speed: Fast (8GB RTX 5060)" + Write-Host " Privacy: 100% local" + Write-Host "" + Write-Host "Claude API (Claude Code):" -ForegroundColor Yellow + Write-Host " Cost: ~`$3-15 per million tokens" + Write-Host " Speed: Depends on network" + Write-Host " Limits: Monthly cap" + Write-Host "" + Write-Host "Recommendation: Use Aider for routine coding, Claude for complex architecture" -ForegroundColor Cyan +} + +Write-Host "Aider helpers loaded! Type 'aider-status' for quick start." -ForegroundColor Green diff --git a/aider-test.py b/aider-test.py new file mode 100644 index 0000000..174b9f8 --- /dev/null +++ b/aider-test.py @@ -0,0 +1,20 @@ +# Simple calculator functions +from typing import Union + +def add(a: int, b: int) -> int: + """Add two integers.""" + return a + b + +def subtract(a: int, b: int) -> int: + """Subtract two integers.""" + return a - b + +def multiply(a: int, b: int) -> int: + """Multiply two integers.""" + return a * b + +def divide(a: Union[int, float], b: Union[int, float]) -> Union[int, float]: + """Divide two numbers. Raises ValueError if divisor is zero.""" + if b == 0: + raise ValueError("Cannot divide by zero.") + return a / b diff --git a/alert-investigation-2026-02-03.md b/alert-investigation-2026-02-03.md new file mode 100644 index 0000000..9c68f24 --- /dev/null +++ b/alert-investigation-2026-02-03.md @@ -0,0 +1,157 @@ +# Prometheus Alert Investigation - Feb 3, 2026 + +## ๐Ÿ” Investigation Summary + +**Time:** 1:58 PM CST +**Investigator:** OpenClaw (Funky) +**Scope:** 4 hosts showing as DOWN in Prometheus + +--- + +## ๐Ÿ“Š Findings + +### 1. pve-router (10.0.10.2) - Proxmox Host +**Status:** โš ๏ธ Host UP, Monitoring DOWN +**Issue:** node_exporter not responding on port 9100 + +``` +โœ… ICMP ping: Responding (0.4ms latency) +โŒ node_exporter (port 9100): Timeout after 2 seconds +``` + +**Diagnosis:** +- Host is online and reachable +- node_exporter service likely stopped or not installed +- This is your office Proxmox host (i5) + +**Action Required:** +```bash +ssh root@10.0.10.2 +systemctl status prometheus-node-exporter +systemctl start prometheus-node-exporter +systemctl enable prometheus-node-exporter +``` + +--- + +### 2. vps-gaming (51.222.12.162) - OVH Gaming VPS +**Status:** โš ๏ธ Host UP, Monitoring DOWN +**Issue:** node_exporter not responding on port 9100 + +``` +โœ… ICMP ping: Responding (23.8ms latency - normal for OVH Canada) +โŒ node_exporter (port 9100): Timeout after 2 seconds +``` + +**Diagnosis:** +- Host is online (WireGuard VPN likely working) +- node_exporter either not installed or firewall blocking port 9100 +- Provider: OVH (deadeyeg4ming.vip) + +**Action Required:** +```bash +ssh root@51.222.12.162 +# Check if installed +systemctl status prometheus-node-exporter + +# If not installed +apt update && apt install prometheus-node-exporter -y + +# Check firewall +ufw status +ufw allow 9100/tcp # If using UFW +``` + +--- + +### 3. OpenClaw Gateway (10.0.10.41) - CT 130 +**Status:** ๐Ÿ”ด Host DOWN / Missing node_exporter +**Issue:** Container reachable but node_exporter not installed + +**Note:** This is the OpenClaw container (me!) - node_exporter should be installed for self-monitoring. + +**Action Required:** +```bash +ssh root@10.0.10.41 +apt update && apt install prometheus-node-exporter -y +systemctl enable --now prometheus-node-exporter +``` + +--- + +### 4. Available Container (10.0.10.42) - CT 131 +**Status:** ๐ŸŸข Available for use +**Issue:** Container available but not yet deployed + +**Note:** This container is available for future use. + +--- + +## ๐ŸŽฏ Priority Action Items + +### Critical (Affects Real Monitoring) +1. **Fix pve-router node_exporter** - This is a production Proxmox host +2. **Fix vps-gaming node_exporter** - This is your WireGuard VPN endpoint + +### Low Priority (Game Servers) +3. **Decide on minecraft-forge** - Start if needed, or remove from Prometheus config +4. **Decide on minecraft-stoneblock** - Start if needed, or remove from Prometheus config + +--- + +## ๐Ÿ”ง Quick Fix Commands + +### For pve-router (10.0.10.2) +```bash +ssh root@10.0.10.2 "apt update && apt install prometheus-node-exporter -y && systemctl enable --now prometheus-node-exporter" +``` + +### For vps-gaming (51.222.12.162) +```bash +ssh root@51.222.12.162 "apt update && apt install prometheus-node-exporter -y && systemctl enable --now prometheus-node-exporter && ufw allow 9100/tcp" +``` + +### Clean Up Prometheus Config (Remove Game Servers) +If you don't want to monitor stopped game servers: +```bash +ssh root@10.0.10.25 +nano /etc/prometheus/prometheus.yml +# Comment out or remove the minecraft targets (10.0.10.41, 10.0.10.42) +systemctl reload prometheus +``` + +--- + +## ๐Ÿ“ˆ Expected Outcome + +**After fixes:** +- โœ… 2/4 hosts back online (pve-router, vps-gaming) +- โœ… Only real infrastructure monitored +- โœ… No false positive alerts +- โœ… Inbox stays clean + +**Time to fix:** ~5 minutes total + +--- + +## ๐Ÿšจ Current Alert Status + +These hosts are **NOT firing critical Discord alerts** yet because: +- They're in "pending" state (less than 2 minutes down) +- Our threshold is **2+ minutes** before triggering + +If you don't fix them, you'll get Discord alerts in: +- **~1-2 minutes** from now (they've been down for a while already) + +--- + +## Notes + +- pve-router and vps-gaming are **real issues** - these should be monitored +- Minecraft servers are probably **intentional** - you don't run them 24/7 +- Consider removing game servers from Prometheus if you don't want to track them + +Let me know if you want me to: +1. Fix the node_exporters remotely (if I have SSH access) +2. Remove game servers from Prometheus config +3. Both! diff --git a/alertmanager-config-updated.yml b/alertmanager-config-updated.yml new file mode 100644 index 0000000..fab7c72 --- /dev/null +++ b/alertmanager-config-updated.yml @@ -0,0 +1,147 @@ +# Alertmanager Configuration for Fred's Homelab - UPDATED +# Location: /etc/prometheus/alertmanager.yml +# Updated: 2026-02-03 (Reduced alert noise) +# +# Changes: +# - Only CRITICAL alerts trigger Discord notifications +# - WARNING alerts are logged but NOT sent to notification channels +# - Removed email notifications entirely + +global: + resolve_timeout: 5m + +# Root route - all alerts enter here +route: + # Group alerts by these labels to reduce noise + group_by: ['alertname', 'severity', 'instance'] + + # Wait 30s before sending first notification (allows grouping) + group_wait: 30s + + # Wait 5min before sending additional alerts for same group + group_interval: 5m + + # Resend alert every 12 hours if still firing + repeat_interval: 12h + + # Default receiver - drops everything (warnings go here) + receiver: 'null' + + # Child routes for specific alert types + routes: + # CRITICAL alerts - send to Discord webhook + - matchers: + - severity="critical" + receiver: 'discord-critical' + group_wait: 10s + repeat_interval: 1h + + # WARNING alerts - explicitly drop (logged by Prometheus, not sent) + - matchers: + - severity="warning" + receiver: 'null' + repeat_interval: 24h + +# Inhibition rules - prevent alert spam +inhibit_rules: + # If critical alert is firing, suppress warnings for same alert + - source_matchers: + - severity="critical" + target_matchers: + - severity="warning" + equal: ['alertname', 'instance'] + + # If host is down, suppress all other alerts from that host + - source_matchers: + - alertname="HostDown" + target_matchers: + - alertname!="HostDown" + equal: ['instance'] + +# Receivers - define where alerts go +receivers: + # Null receiver - drops alerts (used for warnings) + - name: 'null' + + # Discord webhook for CRITICAL alerts + - name: 'discord-critical' + webhook_configs: + - url: 'https://discord.com/api/webhooks/1462667503301038285/ZVJDuek6VADA-RdI09xJDvqjveOWXgxQnMBcsQzoKwVPnNOACMCL5v-HN55-KVe4IZY0' + send_resolved: true + http_config: + follow_redirects: true + max_alerts: 0 # Send all alerts (no limit) + +# ==================================== +# Deployment Instructions +# ==================================== +# +# 1. Backup existing config: +# ssh root@10.0.10.25 'cp /etc/prometheus/alertmanager.yml /etc/prometheus/alertmanager.yml.backup' +# +# 2. Upload this file: +# scp alertmanager-config-updated.yml root@10.0.10.25:/etc/prometheus/alertmanager.yml +# +# 3. Upload updated alert rules: +# scp prometheus-alert-rules-updated.yml root@10.0.10.25:/etc/prometheus/rules/homelab-alerts.yml +# +# 4. Reload Alertmanager: +# ssh root@10.0.10.25 'systemctl reload prometheus-alertmanager' +# +# 5. Reload Prometheus: +# ssh root@10.0.10.25 'systemctl reload prometheus' +# +# 6. Verify configuration: +# curl http://10.0.10.25:9093/api/v1/status +# curl http://10.0.10.25:9090/api/v1/rules +# +# 7. Test Discord webhook: +# curl -X POST http://10.0.10.25:9093/api/v1/alerts -d '[ +# { +# "labels": { +# "alertname": "TestCriticalAlert", +# "severity": "critical", +# "instance": "test:9100" +# }, +# "annotations": { +# "summary": "Test alert - please ignore" +# } +# } +# ]' +# +# ==================================== +# Alert Flow Summary +# ==================================== +# +# CRITICAL alerts: +# Prometheus โ†’ Alertmanager โ†’ Discord Webhook โ†’ Your Discord Server +# +# WARNING alerts: +# Prometheus โ†’ Alertmanager โ†’ null receiver (logged, not sent) +# +# You can view WARNING alerts in: +# - Prometheus UI: http://10.0.10.25:9090/alerts +# - Alertmanager UI: http://10.0.10.25:9093/#/alerts +# +# ==================================== +# Expected Behavior After Update +# ==================================== +# +# Your Discord will ONLY receive: +# โœ… Host completely down (HostDown) +# โœ… CPU >95% for 5 minutes (CriticalCPUUsage) +# โœ… Memory >95% for 5 minutes (CriticalMemoryUsage) +# โœ… Disk <5% free (DiskSpaceCritical) +# โœ… Proxmox node down (ProxmoxNodeDown) +# โœ… PostgreSQL down (PostgreSQLDown) +# โœ… VPS unreachable (VPSDown) +# โœ… Prometheus config reload failed +# +# Your inbox will receive: +# ๐Ÿšซ NOTHING - all email notifications disabled +# +# Warnings (CPU 80-95%, memory 85-95%, etc.): +# ๐Ÿ“Š Logged in Prometheus/Alertmanager UI only +# +# This should dramatically reduce notification noise while still +# catching critical issues that need immediate attention. diff --git a/bible-reading-plan/index.html b/bible-reading-plan/index.html new file mode 100644 index 0000000..0f692d6 --- /dev/null +++ b/bible-reading-plan/index.html @@ -0,0 +1,42 @@ + + + + +Directory listing for /bible-reading-plan/ + + +

Directory listing for /bible-reading-plan/

+
+ +
+ + diff --git a/blackmagic/index.html b/blackmagic/index.html new file mode 100644 index 0000000..e4acb6e --- /dev/null +++ b/blackmagic/index.html @@ -0,0 +1,19 @@ + + + + +Directory listing for /blackmagic/ + + +

Directory listing for /blackmagic/

+
+ +
+ + diff --git a/caddy-home-assistant-fix-v2.txt b/caddy-home-assistant-fix-v2.txt new file mode 100644 index 0000000..5934008 --- /dev/null +++ b/caddy-home-assistant-fix-v2.txt @@ -0,0 +1,13 @@ +# Home Assistant - Version 2 (without Host header override) +bob.nianticbooks.com { + reverse_proxy https://10.0.10.24:8123 { + header_up X-Forwarded-Host {host} + header_up X-Forwarded-Proto {scheme} + header_up X-Real-IP {remote_host} + header_up X-Forwarded-For {remote_host} + + transport http { + tls_insecure_skip_verify + } + } +} diff --git a/caddy-home-assistant-fix.txt b/caddy-home-assistant-fix.txt new file mode 100644 index 0000000..2037b5b --- /dev/null +++ b/caddy-home-assistant-fix.txt @@ -0,0 +1,14 @@ +# Home Assistant +bob.nianticbooks.com { + reverse_proxy https://10.0.10.24:8123 { + header_up Host {http.reverse_proxy.upstream.hostport} + header_up X-Forwarded-Host {host} + header_up X-Forwarded-Proto {scheme} + header_up X-Real-IP {remote_host} + header_up X-Forwarded-For {remote_host} + + transport http { + tls_insecure_skip_verify + } + } +} diff --git a/claude-shared/index.html b/claude-shared/index.html new file mode 100644 index 0000000..3dbbf6c --- /dev/null +++ b/claude-shared/index.html @@ -0,0 +1,23 @@ + + + + +Directory listing for /claude-shared/ + + +

Directory listing for /claude-shared/

+
+ +
+ + diff --git a/copy-to-omv.sh b/copy-to-omv.sh new file mode 100644 index 0000000..1657ff1 --- /dev/null +++ b/copy-to-omv.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# Copy deployment scripts to OMV share +# Run this from your PC/terminal where you have SSH access to Proxmox + +PROXMOX_HOST="10.0.10.3" +OPENCLAW_HOST="10.0.10.28" +OMV_SCRIPTS="/mnt/omv-backups/scripts" + +echo "๐Ÿ“‹ Copying deployment scripts to OMV share..." +echo "" + +# Create scripts directory on Proxmox/OMV +echo "Creating scripts directory..." +ssh root@${PROXMOX_HOST} "mkdir -p ${OMV_SCRIPTS}" + +# Copy deployment script from OpenClaw to OMV +echo "Copying deploy-inline.sh..." +ssh root@${OPENCLAW_HOST} "cat /root/.openclaw/workspace/fred-infrastructure/deploy-inline.sh" | \ + ssh root@${PROXMOX_HOST} "cat > ${OMV_SCRIPTS}/deploy-prometheus-alerts.sh" + +# Make executable +ssh root@${PROXMOX_HOST} "chmod +x ${OMV_SCRIPTS}/deploy-prometheus-alerts.sh" + +echo "" +echo "โœ… Script copied to OMV share!" +echo "" +echo "Location: ${PROXMOX_HOST}:${OMV_SCRIPTS}/deploy-prometheus-alerts.sh" +echo "" +echo "To deploy the alert fix, SSH to Proxmox and run:" +echo " ssh root@${PROXMOX_HOST}" +echo " bash ${OMV_SCRIPTS}/deploy-prometheus-alerts.sh" +echo "" diff --git a/deploy-inline.sh b/deploy-inline.sh new file mode 100644 index 0000000..f7badea --- /dev/null +++ b/deploy-inline.sh @@ -0,0 +1,203 @@ +#!/bin/bash +# Deploy reduced alerts - Run this from your PC/terminal +# Usage: bash deploy-inline.sh + +echo "๐Ÿš€ Deploying alert spam fix to Prometheus..." +echo "" + +# Execute inside Prometheus container via Proxmox +ssh root@10.0.10.3 "pct exec 125 -- bash -s" << 'SCRIPT_END' + +set -e + +echo "๐Ÿ“ฆ Backing up configs..." +mkdir -p /etc/prometheus/backups +cp /etc/prometheus/alertmanager.yml /etc/prometheus/backups/alertmanager.yml.$(date +%Y%m%d-%H%M%S) 2>/dev/null || true +cp /etc/prometheus/rules/homelab-alerts.yml /etc/prometheus/backups/homelab-alerts.yml.$(date +%Y%m%d-%H%M%S) 2>/dev/null || true +echo "โœ… Backups saved" + +echo "๐Ÿ“ Installing new Alertmanager config..." +cat > /etc/prometheus/alertmanager.yml << 'EOF' +global: + resolve_timeout: 5m + +route: + group_by: ['alertname', 'severity', 'instance'] + group_wait: 30s + group_interval: 5m + repeat_interval: 12h + receiver: 'null' + routes: + - matchers: + - severity="critical" + receiver: 'discord-critical' + group_wait: 10s + repeat_interval: 1h + - matchers: + - severity="warning" + receiver: 'null' + repeat_interval: 24h + +inhibit_rules: + - source_matchers: + - severity="critical" + target_matchers: + - severity="warning" + equal: ['alertname', 'instance'] + - source_matchers: + - alertname="HostDown" + target_matchers: + - alertname!="HostDown" + equal: ['instance'] + +receivers: + - name: 'null' + - name: 'discord-critical' + webhook_configs: + - url: 'https://discord.com/api/webhooks/1462667503301038285/ZVJDuek6VADA-RdI09xJDvqjveOWXgxQnMBcsQzoKwVPnNOACMCL5v-HN55-KVe4IZY0' + send_resolved: true + http_config: + follow_redirects: true + max_alerts: 0 +EOF + +echo "โœ… Alertmanager config updated" + +echo "๐Ÿ“ Installing new alert rules..." +cat > /etc/prometheus/rules/homelab-alerts.yml << 'EOF' +groups: + - name: infrastructure + interval: 30s + rules: + - alert: HostDown + expr: up == 0 + for: 2m + labels: + severity: critical + category: infrastructure + annotations: + summary: "Host {{ $labels.instance }} is down" + description: "{{ $labels.instance }} has been unreachable for 2+ minutes" + + - alert: HighCPUUsage + expr: 100 - (avg by(instance, hostname) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80 + for: 5m + labels: + severity: warning + category: performance + annotations: + summary: "High CPU on {{ $labels.hostname }}" + description: "CPU {{ $value | humanize }}%" + + - alert: CriticalCPUUsage + expr: 100 - (avg by(instance, hostname) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 95 + for: 5m + labels: + severity: critical + category: performance + annotations: + summary: "CRITICAL CPU on {{ $labels.hostname }}" + description: "CPU {{ $value | humanize }}%" + + - alert: HighMemoryUsage + expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 85 + for: 10m + labels: + severity: warning + category: performance + annotations: + summary: "High memory on {{ $labels.hostname }}" + description: "Memory {{ $value | humanize }}%" + + - alert: CriticalMemoryUsage + expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 95 + for: 5m + labels: + severity: critical + category: performance + annotations: + summary: "CRITICAL memory on {{ $labels.hostname }}" + description: "Memory {{ $value | humanize }}%" + + - name: storage + interval: 1m + rules: + - alert: DiskSpaceLow + expr: (node_filesystem_avail_bytes{fstype!~"tmpfs|fuse.lxcfs|squashfs|overlay"} / node_filesystem_size_bytes) * 100 < 15 + for: 5m + labels: + severity: warning + category: storage + annotations: + summary: "Low disk on {{ $labels.hostname }}" + description: "{{ $labels.mountpoint }} has {{ $value | humanize }}% free" + + - alert: DiskSpaceCritical + expr: (node_filesystem_avail_bytes{fstype!~"tmpfs|fuse.lxcfs|squashfs|overlay"} / node_filesystem_size_bytes) * 100 < 5 + for: 2m + labels: + severity: critical + category: storage + annotations: + summary: "CRITICAL disk on {{ $labels.hostname }}" + description: "{{ $labels.mountpoint }} only {{ $value | humanize }}% free!" + + - name: services + interval: 1m + rules: + - alert: PostgreSQLDown + expr: up{app="postgres"} == 0 + for: 2m + labels: + severity: critical + category: database + annotations: + summary: "PostgreSQL is down" + description: "PostgreSQL down for 2+ minutes" + + - alert: VPSDown + expr: up{role="vps"} == 0 + for: 2m + labels: + severity: critical + category: infrastructure + annotations: + summary: "VPS unreachable" + description: "VPS down for 2+ minutes" + + - alert: ProxmoxNodeDown + expr: up{role="proxmox-host"} == 0 + for: 2m + labels: + severity: critical + category: infrastructure + annotations: + summary: "Proxmox node down" + description: "Proxmox host unreachable for 2+ minutes" +EOF + +echo "โœ… Alert rules updated" + +echo "๐Ÿ”„ Reloading services..." +systemctl reload prometheus +systemctl reload prometheus-alertmanager + +echo "" +echo "โœ… DEPLOYMENT COMPLETE!" +echo "" +echo "๐Ÿ“Š Changes applied:" +echo " โ€ข CPU warning: 80%+ over 5min (logged only)" +echo " โ€ข CPU critical: 95%+ over 5min (Discord)" +echo " โ€ข Only CRITICAL alerts โ†’ Discord" +echo " โ€ข WARNING alerts โ†’ Logged, not sent" +echo " โ€ข Email notifications โ†’ DISABLED" +echo "" +echo "Your inbox spam should STOP immediately!" +echo "" +echo "๐Ÿงช Test Discord webhook:" +echo " curl -X POST http://localhost:9093/api/v1/alerts -d '[{\"labels\":{\"alertname\":\"Test\",\"severity\":\"critical\"},\"annotations\":{\"summary\":\"Test alert\"}}]'" + +SCRIPT_END + +echo "" +echo "๐ŸŽ‰ Done! Check your Discord for the test alert." diff --git a/deploy-reduced-alerts.sh b/deploy-reduced-alerts.sh new file mode 100755 index 0000000..56806db --- /dev/null +++ b/deploy-reduced-alerts.sh @@ -0,0 +1,82 @@ +#!/bin/bash +# Deploy Reduced Alert Configuration +# Updates Prometheus alert rules and Alertmanager config to reduce notification noise +# Only CRITICAL alerts trigger Discord notifications + +set -e + +PROMETHEUS_HOST="10.0.10.25" +PROMETHEUS_USER="root" + +echo "๐Ÿš€ Deploying reduced alert configuration to Prometheus..." +echo "" + +# Check if files exist +if [ ! -f "prometheus-alert-rules-updated.yml" ]; then + echo "โŒ Error: prometheus-alert-rules-updated.yml not found" + exit 1 +fi + +if [ ! -f "alertmanager-config-updated.yml" ]; then + echo "โŒ Error: alertmanager-config-updated.yml not found" + exit 1 +fi + +# Backup existing configs +echo "๐Ÿ“ฆ Backing up existing configurations..." +ssh ${PROMETHEUS_USER}@${PROMETHEUS_HOST} 'mkdir -p /etc/prometheus/backups' +ssh ${PROMETHEUS_USER}@${PROMETHEUS_HOST} "cp /etc/prometheus/alertmanager.yml /etc/prometheus/backups/alertmanager.yml.$(date +%Y%m%d-%H%M%S)" 2>/dev/null || true +ssh ${PROMETHEUS_USER}@${PROMETHEUS_HOST} "cp /etc/prometheus/rules/homelab-alerts.yml /etc/prometheus/backups/homelab-alerts.yml.$(date +%Y%m%d-%H%M%S)" 2>/dev/null || true +echo "โœ… Backups created in /etc/prometheus/backups/" +echo "" + +# Upload new configs +echo "๐Ÿ“ค Uploading new configurations..." +scp alertmanager-config-updated.yml ${PROMETHEUS_USER}@${PROMETHEUS_HOST}:/etc/prometheus/alertmanager.yml +scp prometheus-alert-rules-updated.yml ${PROMETHEUS_USER}@${PROMETHEUS_HOST}:/etc/prometheus/rules/homelab-alerts.yml +echo "โœ… Files uploaded" +echo "" + +# Reload services +echo "๐Ÿ”„ Reloading Prometheus and Alertmanager..." +ssh ${PROMETHEUS_USER}@${PROMETHEUS_HOST} 'systemctl reload prometheus' +ssh ${PROMETHEUS_USER}@${PROMETHEUS_HOST} 'systemctl reload prometheus-alertmanager' +echo "โœ… Services reloaded" +echo "" + +# Verify configuration +echo "๐Ÿ” Verifying configuration..." +echo "" +echo "Prometheus status:" +ssh ${PROMETHEUS_USER}@${PROMETHEUS_HOST} 'systemctl status prometheus --no-pager -l | head -10' +echo "" +echo "Alertmanager status:" +ssh ${PROMETHEUS_USER}@${PROMETHEUS_HOST} 'systemctl status prometheus-alertmanager --no-pager -l | head -10' +echo "" + +# Test API endpoints +echo "Testing API endpoints..." +echo "" +echo "Prometheus rules API:" +curl -s http://${PROMETHEUS_HOST}:9090/api/v1/rules | head -200 +echo "" +echo "Alertmanager status API:" +curl -s http://${PROMETHEUS_HOST}:9093/api/v1/status | head -100 +echo "" + +echo "โœ… Deployment complete!" +echo "" +echo "๐Ÿ“Š Summary of changes:" +echo " โ€ข CPU alert threshold: 80%+ over 5 minutes (warning)" +echo " โ€ข CPU critical threshold: 95%+ over 5 minutes (notification)" +echo " โ€ข Only CRITICAL alerts sent to Discord" +echo " โ€ข WARNING alerts logged but NOT sent" +echo " โ€ข Email notifications completely disabled" +echo "" +echo "๐Ÿ”— Check alert status:" +echo " Prometheus: http://${PROMETHEUS_HOST}:9090/alerts" +echo " Alertmanager: http://${PROMETHEUS_HOST}:9093/#/alerts" +echo "" +echo "๐Ÿงช Test critical alert (sends to Discord):" +echo " curl -X POST http://${PROMETHEUS_HOST}:9093/api/v1/alerts -d '[{\"labels\":{\"alertname\":\"TestCriticalAlert\",\"severity\":\"critical\",\"instance\":\"test:9100\"},\"annotations\":{\"summary\":\"Test - please ignore\"}}]'" +echo "" diff --git a/deploy-uptime-kuma.sh b/deploy-uptime-kuma.sh new file mode 100644 index 0000000..eac0d26 --- /dev/null +++ b/deploy-uptime-kuma.sh @@ -0,0 +1,134 @@ +#!/bin/bash +# Deploy Uptime Kuma on main-pve +# LXC Container 128 at 10.0.10.26 + +set -e + +echo "=== Uptime Kuma Deployment Script ===" +echo "" + +# Configuration +VMID=128 +HOSTNAME="uptime-kuma" +IP="10.0.10.26" +GATEWAY="10.0.10.1" +CORES=2 +MEMORY=2048 +SWAP=512 +DISK="8" +TEMPLATE="local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst" + +echo "Configuration:" +echo " VMID: $VMID" +echo " Hostname: $HOSTNAME" +echo " IP: $IP/24" +echo " Resources: ${CORES} cores, ${MEMORY}MB RAM, ${DISK}GB disk" +echo "" + +# Check if container already exists +if ssh root@10.0.10.3 "pct status $VMID 2>/dev/null"; then + echo "โš ๏ธ Container $VMID already exists!" + echo "" + read -p "Delete and recreate? (y/N): " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + echo "Stopping and removing container $VMID..." + ssh root@10.0.10.3 "pct stop $VMID 2>/dev/null || true" + sleep 2 + ssh root@10.0.10.3 "pct destroy $VMID" + echo "โœ… Container removed" + else + echo "โŒ Aborted" + exit 1 + fi +fi + +echo "Creating LXC container..." +ssh root@10.0.10.3 << EOF +pct create $VMID $TEMPLATE \ + --hostname $HOSTNAME \ + --cores $CORES \ + --memory $MEMORY \ + --swap $SWAP \ + --net0 name=eth0,bridge=vmbr0,ip=$IP/24,gw=$GATEWAY \ + --storage local-lvm \ + --rootfs 8 \ + --unprivileged 1 \ + --features nesting=1 + +echo "โœ… Container created" +echo "" + +echo "Starting container..." +pct start $VMID +sleep 5 + +echo "โœ… Container started" +echo "" + +echo "Installing Docker..." +pct exec $VMID -- bash -c ' +apt update +apt install -y docker.io curl +systemctl enable docker +systemctl start docker +' + +echo "โœ… Docker installed" +echo "" + +echo "Deploying Uptime Kuma..." +pct exec $VMID -- bash -c " +docker run -d \ + --name uptime-kuma \ + --restart=always \ + -p 3001:3001 \ + -v uptime-kuma:/app/data \ + louislam/uptime-kuma:2 + +echo 'Waiting for Uptime Kuma to start...' +sleep 10 + +# Verify container is running +if docker ps | grep -q uptime-kuma; then + echo 'โœ… Uptime Kuma container running' +else + echo 'โŒ Uptime Kuma failed to start' + docker logs uptime-kuma + exit 1 +fi +" + +echo "" +echo "Testing Uptime Kuma endpoint..." +sleep 5 +EOF + +# Test from host +if curl -s -o /dev/null -w "%{http_code}" http://$IP:3001 | grep -q 200; then + echo "โœ… Uptime Kuma is responding!" +else + echo "โš ๏ธ Uptime Kuma not responding yet (may need more time to start)" +fi + +echo "" +echo "=== Deployment Complete! ===" +echo "" +echo "๐ŸŽ‰ Uptime Kuma deployed successfully!" +echo "" +echo "Access Uptime Kuma:" +echo " URL: http://$IP:3001" +echo " Initial setup required on first visit" +echo "" +echo "Next steps:" +echo "1. Go to http://$IP:3001" +echo "2. Create admin account" +echo "3. Add monitors for your services" +echo "4. Create status page" +echo "" +echo "Container management:" +echo " Start: ssh root@10.0.10.3 'pct start $VMID'" +echo " Stop: ssh root@10.0.10.3 'pct stop $VMID'" +echo " Shell: ssh root@10.0.10.3 'pct enter $VMID'" +echo " Logs: ssh root@10.0.10.3 'pct exec $VMID -- docker logs uptime-kuma -f'" +echo "" diff --git a/fred-workspace 2.code-workspace b/fred-workspace 2.code-workspace new file mode 100644 index 0000000..2066888 --- /dev/null +++ b/fred-workspace 2.code-workspace @@ -0,0 +1,36 @@ +{ + "folders": [ + { + "name": "๐ŸŽฏ Claude Workflows", + "path": "claude-workflows" + }, + { + "name": "๐Ÿฅ VA Strategy", + "path": "VA-Strategy" + }, + { + "name": "๐Ÿ  Infrastructure", + "path": "infrastructure" + }, + { + "name": "โš™๏ธ Config", + "path": "config" + } +], + "settings": { + "files.exclude": { + "**/node_modules": true, + "**/.git": false, + "**/claude-code-history": true + }, + "search.exclude": { + "**/node_modules": true, + "**/claude-code-history": true + }, + "files.watcherExclude": { + "**/node_modules/**": true, + "**/claude-code-history/**": true + }, + "powershell.cwd": "๐Ÿ  Infrastructure" + } +} diff --git a/fred-workspace.code-workspace b/fred-workspace.code-workspace new file mode 100644 index 0000000..29358b3 --- /dev/null +++ b/fred-workspace.code-workspace @@ -0,0 +1,36 @@ +{ + "folders": [ + { + "path": "claude-workflows", + "name": "๐ŸŽฏ Claude Workflows" + }, + { + "path": "VA-Strategy", + "name": "๐Ÿฅ VA Strategy" + }, + { + "path": "infrastructure", + "name": "๐Ÿ  Infrastructure" + }, + { + "path": "config", + "name": "โš™๏ธ Config" + } + ], + "settings": { + "files.exclude": { + "**/node_modules": true, + "**/.git": false, + "**/claude-code-history": true + }, + "search.exclude": { + "**/node_modules": true, + "**/claude-code-history": true + }, + "files.watcherExclude": { + "**/node_modules/**": true, + "**/claude-code-history/**": true + }, + "powershell.cwd": "๐Ÿ  Infrastructure" + } +} diff --git a/gather-container-info.sh b/gather-container-info.sh new file mode 100644 index 0000000..46cf3b9 --- /dev/null +++ b/gather-container-info.sh @@ -0,0 +1,75 @@ +#!/bin/bash +# Gather information about all undocumented containers on main-pve + +echo "=== Container Information Gathering ===" +echo "" + +# List of undocumented containers +CONTAINERS="103 105 108 109 113 114 115 200" +VM="110" + +ssh root@10.0.10.3 << 'EOF' +echo "VMs:" +echo "====" +echo "" + +# VM 110 +echo "VM 110 (debian):" +qm config 110 | grep -E "^(cores|memory|net0|bootdisk):" +echo "" + +echo "Containers:" +echo "===========" +echo "" + +for CT in 103 105 108 109 113 114 115 200; do + echo "CT $CT:" + + # Get container name + NAME=$(pct config $CT | grep "^hostname:" | awk '{print $2}') + echo " Name: $NAME" + + # Get IP address + IP=$(pct config $CT | grep "^net0:" | grep -oP 'ip=\K[^,]+' || echo "DHCP") + echo " IP: $IP" + + # Get resources + CORES=$(pct config $CT | grep "^cores:" | awk '{print $2}') + MEM=$(pct config $CT | grep "^memory:" | awk '{print $2}') + echo " Resources: ${CORES} cores, ${MEM}MB RAM" + + # Check if running + STATUS=$(pct status $CT | awk '{print $2}') + echo " Status: $STATUS" + + # Try to get running processes if container is running + if [ "$STATUS" = "running" ]; then + echo " Top processes:" + pct exec $CT -- ps aux --sort=-%mem | head -6 | tail -5 | awk '{printf " - %s (PID %s, MEM %s%%)\n", $11, $2, $4}' + + # Check for Docker + if pct exec $CT -- which docker >/dev/null 2>&1; then + echo " Docker containers:" + pct exec $CT -- docker ps --format " - {{.Names}} ({{.Image}})" 2>/dev/null || echo " (Docker installed but no containers)" + fi + + # Check listening ports + echo " Listening ports:" + pct exec $CT -- ss -tlnp 2>/dev/null | grep LISTEN | awk '{print " - " $4}' | head -5 + fi + + echo "" +done + +echo "" +echo "=== Quick Summary ===" +echo "" +echo "Undocumented containers:" +for CT in 103 105 108 109 113 114 115 200; do + NAME=$(pct config $CT | grep "^hostname:" | awk '{print $2}') + IP=$(pct config $CT | grep "^net0:" | grep -oP 'ip=\K[^,]+' || echo "DHCP") + STATUS=$(pct status $CT | awk '{print $2}') + printf "CT %3d: %-20s %-15s %s\n" $CT "$NAME" "$IP" "$STATUS" +done + +EOF diff --git a/home-assistant-http-fix.txt b/home-assistant-http-fix.txt new file mode 100644 index 0000000..7e57ae7 --- /dev/null +++ b/home-assistant-http-fix.txt @@ -0,0 +1,8 @@ +http: + use_x_forwarded_for: true + trusted_proxies: + - 127.0.0.1 + - 10.0.8.1 + + ssl_certificate: /config/fullchain.pem + ssl_key: /config/privkey.pem diff --git a/home-assistant/index.html b/home-assistant/index.html new file mode 100644 index 0000000..560c209 --- /dev/null +++ b/home-assistant/index.html @@ -0,0 +1,17 @@ + + + + +Directory listing for /home-assistant/ + + +

Directory listing for /home-assistant/

+
+ +
+ + diff --git a/import-cocktaildb-to-bar-assistant-v2.py b/import-cocktaildb-to-bar-assistant-v2.py new file mode 100644 index 0000000..b574141 --- /dev/null +++ b/import-cocktaildb-to-bar-assistant-v2.py @@ -0,0 +1,286 @@ +#!/usr/bin/env python3 +""" +Import all cocktails from TheCocktailDB into Bar Assistant + +Requirements: + pip install requests + +Usage: + python import-cocktaildb-to-bar-assistant-v2.py +""" + +import requests +import time +import json +import re +from typing import Dict, List, Optional, Tuple + +# Configuration +BAR_ASSISTANT_URL = "https://cocktails.nianticbooks.com" +BAR_ASSISTANT_API = f"{BAR_ASSISTANT_URL}/bar/api" +COCKTAILDB_API = "https://www.thecocktaildb.com/api/json/v1/1" + +BAR_ASSISTANT_TOKEN = "3|dqtZPWZoKdU59fFRLkGrL83I9HKm9EqmLCgO4kkI96199a82" +BAR_ID = 1 + +# Cache for ingredients +ingredient_cache = {} + + +def parse_amount(measure_str: str) -> Tuple[Optional[float], str]: + """Parse amount string like '2 oz' into (2.0, 'oz')""" + if not measure_str or not measure_str.strip(): + return (None, "") + + measure_str = measure_str.strip() + + # Try to extract number and unit + # Handles: "2 oz", "1/2 oz", "1.5 oz", "2", etc. + match = re.match(r'^([\d./\s]+)\s*(.*)$', measure_str) + + if match: + amount_str = match.group(1).strip() + unit = match.group(2).strip() + + # Handle fractions like "1/2" or "1 1/2" + try: + # Split on spaces to handle mixed fractions + parts = amount_str.split() + total = 0.0 + + for part in parts: + if '/' in part: + # Handle fraction + num, denom = part.split('/') + total += float(num) / float(denom) + else: + # Handle whole number or decimal + total += float(part) + + return (total, unit if unit else "oz") + except: + pass + + # If we can't parse, return None + return (None, "") + + +def get_or_create_ingredient(name: str, headers: Dict) -> Optional[int]: + """Get ingredient ID by name, or create if it doesn't exist""" + + # Check cache first + name_lower = name.lower().strip() + if name_lower in ingredient_cache: + return ingredient_cache[name_lower] + + # Search for ingredient + url = f"{BAR_ASSISTANT_API}/ingredients" + params = {"filter[name]": name} + + try: + response = requests.get(url, params=params, headers=headers, timeout=10) + response.raise_for_status() + data = response.json() + + # Check if we found a match + if data.get('data') and len(data['data']) > 0: + for ingredient in data['data']: + if ingredient['name'].lower() == name_lower: + ingredient_id = ingredient['id'] + ingredient_cache[name_lower] = ingredient_id + return ingredient_id + + # Ingredient not found, create it + print(f" Creating new ingredient: {name}") + create_data = { + "name": name, + "strength": 0, + "description": f"Imported from TheCocktailDB" + } + + response = requests.post(url, json=create_data, headers=headers, timeout=10) + response.raise_for_status() + ingredient_id = response.json()['data']['id'] + ingredient_cache[name_lower] = ingredient_id + return ingredient_id + + except Exception as e: + print(f" [!] Error with ingredient '{name}': {e}") + return None + + +def get_all_cocktails_from_cocktaildb() -> List[Dict]: + """Fetch all cocktails from TheCocktailDB""" + print("Fetching all cocktails from TheCocktailDB...") + all_cocktails = [] + + letters = 'abcdefghijklmnopqrstuvwxyz0123456789' + + for letter in letters: + print(f" Fetching cocktails starting with '{letter}'...") + url = f"{COCKTAILDB_API}/search.php?f={letter}" + + try: + response = requests.get(url, timeout=10) + response.raise_for_status() + data = response.json() + + if data.get('drinks'): + cocktails = data['drinks'] + all_cocktails.extend(cocktails) + print(f" Found {len(cocktails)} cocktails") + + time.sleep(0.5) + + except Exception as e: + print(f" Error fetching '{letter}': {e}") + continue + + print(f"\n[OK] Total cocktails found: {len(all_cocktails)}") + return all_cocktails + + +def convert_cocktail_ingredients(cocktail: Dict, headers: Dict) -> List[Dict]: + """Extract and convert ingredients from TheCocktailDB format""" + ingredients = [] + + for i in range(1, 16): + ingredient_key = f"strIngredient{i}" + measure_key = f"strMeasure{i}" + + ingredient_name = cocktail.get(ingredient_key) + measure = cocktail.get(measure_key, "") + + if ingredient_name and ingredient_name.strip(): + # Get or create the ingredient + ingredient_id = get_or_create_ingredient(ingredient_name.strip(), headers) + + if ingredient_id: + # Parse the amount + amount, units = parse_amount(measure) + + ingredients.append({ + "sort": i, # Add sort field for ingredient order + "ingredient_id": ingredient_id, + "amount": amount if amount is not None else 0, + "units": units if units else "oz", # Default to "oz" if no unit specified + "optional": False + }) + + return ingredients + + +def import_cocktail(cocktail: Dict, headers: Dict) -> bool: + """Import a single cocktail into Bar Assistant""" + + # Convert ingredients first + ingredients = convert_cocktail_ingredients(cocktail, headers) + + if not ingredients: + print(f" [!] No valid ingredients found") + return False + + # Build cocktail data + cocktail_data = { + "name": cocktail.get('strDrink', 'Unknown'), + "instructions": cocktail.get('strInstructions', ''), + "description": f"Imported from TheCocktailDB. Category: {cocktail.get('strCategory', 'N/A')}. {cocktail.get('strAlcoholic', '')}", + "source": f"TheCocktailDB (ID: {cocktail.get('idDrink')})", + "garnish": cocktail.get('strGarnish', ''), + "glass": cocktail.get('strGlass', ''), + "ingredients": ingredients + # Note: Images are not imported - Bar Assistant expects image IDs, not URLs + } + + # Add tags + tags = [] + if cocktail.get('strCategory'): + tags.append(cocktail['strCategory']) + if cocktail.get('strAlcoholic'): + tags.append(cocktail['strAlcoholic']) + if cocktail.get('strIBA'): + tags.append(cocktail['strIBA']) + + if tags: + cocktail_data['tags'] = tags + + # Import + url = f"{BAR_ASSISTANT_API}/cocktails" + + try: + response = requests.post(url, json=cocktail_data, headers=headers, timeout=30) + response.raise_for_status() + return True + except requests.exceptions.HTTPError as e: + if e.response.status_code == 409: + print(f" [!] Already exists") + return False + elif e.response.status_code == 422: + print(f" [X] Validation error: {e.response.text[:200]}") + return False + else: + print(f" [X] HTTP Error {e.response.status_code}") + return False + except Exception as e: + print(f" [X] Error: {e}") + return False + + +def main(): + """Main execution""" + print("="*60) + print("TheCocktailDB -> Bar Assistant Bulk Import") + print("="*60) + + # Set up headers + headers = { + "Authorization": f"Bearer {BAR_ASSISTANT_TOKEN}", + "Content-Type": "application/json", + "Accept": "application/json", + "Bar-Assistant-Bar-Id": str(BAR_ID) + } + + # Step 1: Fetch all cocktails from TheCocktailDB + cocktails = get_all_cocktails_from_cocktaildb() + + if not cocktails: + print("\n[ERROR] No cocktails found from TheCocktailDB") + return + + # Step 2: Import + print(f"\nImporting {len(cocktails)} cocktails to Bar Assistant...") + print("This may take a while...\n") + + success_count = 0 + skip_count = 0 + error_count = 0 + + for idx, cocktail in enumerate(cocktails, 1): + cocktail_name = cocktail.get('strDrink', 'Unknown') + print(f"[{idx}/{len(cocktails)}] {cocktail_name}...") + + if import_cocktail(cocktail, headers): + success_count += 1 + print(f" [OK] Imported successfully") + else: + skip_count += 1 + + time.sleep(0.5) + + # Summary + print("\n" + "="*60) + print("Import Complete!") + print("="*60) + print(f"[OK] Successfully imported: {success_count}") + print(f"[!] Skipped (duplicates/errors): {skip_count}") + print(f"\nTotal processed: {len(cocktails)}") + print(f"\nView your cocktails at: {BAR_ASSISTANT_URL}") + + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + print("\n\n[!] Import cancelled by user") + except Exception as e: + print(f"\n\n[ERROR] Error: {e}") diff --git a/import-cocktaildb-to-bar-assistant.py b/import-cocktaildb-to-bar-assistant.py new file mode 100644 index 0000000..b417de1 --- /dev/null +++ b/import-cocktaildb-to-bar-assistant.py @@ -0,0 +1,201 @@ +#!/usr/bin/env python3 +""" +Import all cocktails from TheCocktailDB into Bar Assistant + +Requirements: + pip install requests + +Usage: + python import-cocktaildb-to-bar-assistant.py +""" + +import requests +import time +import json +from typing import Dict, List + +# Configuration +BAR_ASSISTANT_URL = "https://cocktails.nianticbooks.com" +BAR_ASSISTANT_API = f"{BAR_ASSISTANT_URL}/bar/api" +COCKTAILDB_API = "https://www.thecocktaildb.com/api/json/v1/1" + +# You'll need to get these from Bar Assistant +# Login to Bar Assistant, go to Settings -> API Tokens, create a token +BAR_ASSISTANT_TOKEN = "3|dqtZPWZoKdU59fFRLkGrL83I9HKm9EqmLCgO4kkI96199a82" # Set this to your API token +BAR_ID = 1 # Usually 1 for your first bar + + +def get_all_cocktails_from_cocktaildb() -> List[Dict]: + """Fetch all cocktails from TheCocktailDB""" + print("Fetching all cocktails from TheCocktailDB...") + all_cocktails = [] + + # Get all cocktails by first letter (a-z, 0-9) + letters = 'abcdefghijklmnopqrstuvwxyz0123456789' + + for letter in letters: + print(f" Fetching cocktails starting with '{letter}'...") + url = f"{COCKTAILDB_API}/search.php?f={letter}" + + try: + response = requests.get(url, timeout=10) + response.raise_for_status() + data = response.json() + + if data.get('drinks'): + cocktails = data['drinks'] + all_cocktails.extend(cocktails) + print(f" Found {len(cocktails)} cocktails") + + time.sleep(0.5) # Be nice to the API + + except Exception as e: + print(f" Error fetching '{letter}': {e}") + continue + + print(f"\n[OK] Total cocktails found: {len(all_cocktails)}") + return all_cocktails + + +def convert_cocktaildb_to_bar_assistant(cocktail: Dict) -> Dict: + """Convert TheCocktailDB format to Bar Assistant format""" + + # Extract ingredients + ingredients = [] + for i in range(1, 16): # TheCocktailDB supports up to 15 ingredients + ingredient_key = f"strIngredient{i}" + measure_key = f"strMeasure{i}" + + ingredient_name = cocktail.get(ingredient_key) + measure = cocktail.get(measure_key) + + if ingredient_name and ingredient_name.strip(): + ingredients.append({ + "name": ingredient_name.strip(), + "amount": measure.strip() if measure else "", + "units": "", + "optional": False + }) + + # Build the cocktail data + bar_assistant_cocktail = { + "name": cocktail.get('strDrink', 'Unknown'), + "instructions": cocktail.get('strInstructions', ''), + "description": f"Imported from TheCocktailDB. Category: {cocktail.get('strCategory', 'N/A')}", + "source": f"TheCocktailDB (ID: {cocktail.get('idDrink')})", + "garnish": cocktail.get('strGarnish', ''), + "glass": cocktail.get('strGlass', ''), + "method": cocktail.get('strCategory', ''), + "tags": [ + cocktail.get('strCategory', ''), + cocktail.get('strAlcoholic', ''), + cocktail.get('strIBA', '') if cocktail.get('strIBA') else None + ], + "ingredients": ingredients, + "images": [ + {"url": cocktail.get('strDrinkThumb'), "copyright": "TheCocktailDB"} + ] if cocktail.get('strDrinkThumb') else [] + } + + # Clean up None values from tags + bar_assistant_cocktail['tags'] = [tag for tag in bar_assistant_cocktail['tags'] if tag] + + return bar_assistant_cocktail + + +def import_to_bar_assistant(cocktail_data: Dict, headers: Dict) -> bool: + """Import a cocktail into Bar Assistant via API""" + url = f"{BAR_ASSISTANT_API}/cocktails" + + try: + response = requests.post(url, json=cocktail_data, headers=headers, timeout=30) + response.raise_for_status() + return True + except requests.exceptions.HTTPError as e: + if e.response.status_code == 409: + print(f" [!] Already exists: {cocktail_data['name']}") + else: + print(f" [X] HTTP Error {e.response.status_code}: {cocktail_data['name']}") + return False + except Exception as e: + print(f" [X] Error importing {cocktail_data['name']}: {e}") + return False + + +def main(): + """Main execution""" + print("="*60) + print("TheCocktailDB -> Bar Assistant Bulk Import") + print("="*60) + + # Check if token is set + if not BAR_ASSISTANT_TOKEN: + print("\n[ERROR] ERROR: BAR_ASSISTANT_TOKEN not set!") + print("\nTo get your API token:") + print("1. Login to Bar Assistant at https://cocktails.nianticbooks.com") + print("2. Go to Settings (or Profile)") + print("3. Find 'Personal Access Tokens' or 'API Tokens'") + print("4. Create a new token") + print("5. Copy the token and set it in this script") + print("\nThen edit this script and set:") + print(" BAR_ASSISTANT_TOKEN = 'your-token-here'") + return + + # Set up headers + headers = { + "Authorization": f"Bearer {BAR_ASSISTANT_TOKEN}", + "Content-Type": "application/json", + "Accept": "application/json", + "Bar-Assistant-Bar-Id": str(BAR_ID) + } + + # Step 1: Fetch all cocktails from TheCocktailDB + cocktails = get_all_cocktails_from_cocktaildb() + + if not cocktails: + print("\n[ERROR] No cocktails found from TheCocktailDB") + return + + # Step 2: Convert and import + print(f"\nImporting {len(cocktails)} cocktails to Bar Assistant...") + print("This may take a while...\n") + + success_count = 0 + skip_count = 0 + error_count = 0 + + for idx, cocktail in enumerate(cocktails, 1): + cocktail_name = cocktail.get('strDrink', 'Unknown') + print(f"[{idx}/{len(cocktails)}] {cocktail_name}...") + + # Convert format + bar_assistant_data = convert_cocktaildb_to_bar_assistant(cocktail) + + # Import + if import_to_bar_assistant(bar_assistant_data, headers): + success_count += 1 + print(f" [OK] Imported successfully") + else: + skip_count += 1 + + # Small delay to avoid overwhelming the server + time.sleep(0.5) + + # Summary + print("\n" + "="*60) + print("Import Complete!") + print("="*60) + print(f"[OK] Successfully imported: {success_count}") + print(f"[!] Skipped (duplicates): {skip_count}") + print(f"[X] Errors: {error_count}") + print(f"\nTotal processed: {len(cocktails)}") + print(f"\nView your cocktails at: {BAR_ASSISTANT_URL}") + + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + print("\n\n[!] Import cancelled by user") + except Exception as e: + print(f"\n\n[ERROR] Error: {e}") diff --git a/index.html b/index.html new file mode 100644 index 0000000..b976276 --- /dev/null +++ b/index.html @@ -0,0 +1,76 @@ + + + + +Directory listing for / + + +

Directory listing for /

+
+ +
+ + diff --git a/infrastructure/.env.example b/infrastructure/.env.example new file mode 100644 index 0000000..09b6f01 --- /dev/null +++ b/infrastructure/.env.example @@ -0,0 +1,141 @@ +# Homelab Infrastructure Environment Variables +# Copy to .env and fill in any secrets +# Generated from AGENT-REFERENCE.md + +# ============================================================================= +# VPS HOSTS +# ============================================================================= +VPS_PRIMARY_IP=66.63.182.168 +VPS_PRIMARY_DOMAIN=vps.nianticbooks.com +VPS_PRIMARY_USER=fred + +VPS_GAMING_IP=51.222.12.162 +VPS_GAMING_DOMAIN=deadeyeg4ming.vip +VPS_GAMING_USER=ubuntu + +# ============================================================================= +# PROXMOX HOSTS +# ============================================================================= +PROXMOX_MAIN_IP=10.0.10.3 +PROXMOX_MAIN_HOST=main-pve +PROXMOX_MAIN_USER=root + +PROXMOX_ROUTER_IP=10.0.10.2 +PROXMOX_ROUTER_HOST=pve-router +PROXMOX_ROUTER_USER=root + +PROXMOX_STORAGE_IP=10.0.10.4 +PROXMOX_STORAGE_HOST=pve-storage +PROXMOX_STORAGE_USER=root + +# ============================================================================= +# NETWORK +# ============================================================================= +GATEWAY_IP=10.0.10.1 +NETWORK_CIDR=10.0.10.0/24 +DHCP_RANGE_START=10.0.10.50 +DHCP_RANGE_END=10.0.10.254 + +# WireGuard +WIREGUARD_ACTIVE_NETWORK=10.0.9.0/24 +WIREGUARD_LEGACY_NETWORK=10.0.8.0/24 +WIREGUARD_ENDPOINT=51.222.12.162:51820 +WIREGUARD_VPS_PROXY_IP=10.0.9.3 + +# ============================================================================= +# INFRASTRUCTURE SERVICES +# ============================================================================= +# Step-CA (CT 115) +STEPCA_IP=10.0.10.15 +STEPCA_PORT=8443 +STEPCA_ACME_URL=https://10.0.10.15:8443/acme/acme/directory + +# PostgreSQL (CT 102) - Shared database +POSTGRES_IP=10.0.10.20 +POSTGRES_PORT=5432 +POSTGRES_USER=postgres +POSTGRES_PASSWORD= + +# Authentik (CT 121) +AUTHENTIK_IP=10.0.10.21 +AUTHENTIK_PORT=9000 +AUTHENTIK_ADMIN_USER=akadmin +AUTHENTIK_ADMIN_PASSWORD= + +# n8n (CT 106) +N8N_IP=10.0.10.22 +N8N_PORT=5678 + +# RustDesk (CT 123) +RUSTDESK_IP=10.0.10.23 +RUSTDESK_ID_PORT=21115 +RUSTDESK_RELAY_PORT=21117 +RUSTDESK_PUBKEY=sfYuCTMHxrA22kukomb/RAKYyUgr8iaMfm/U4CFLfL0= + +# Prometheus/Grafana (CT 125) +PROMETHEUS_IP=10.0.10.25 +PROMETHEUS_PORT=9090 +GRAFANA_IP=10.0.10.25 +GRAFANA_PORT=3000 + +# Uptime Kuma (CT 128) +UPTIME_KUMA_IP=10.0.10.26 +UPTIME_KUMA_PORT=3001 + +# ============================================================================= +# APPLICATION SERVICES +# ============================================================================= +# Home Assistant (VM 104 on pve-router) +HOME_ASSISTANT_IP=10.0.10.24 +HOME_ASSISTANT_PORT=8123 + +# Dockge/Vikunja (CT 127) +DOCKGE_IP=10.0.10.27 +DOCKGE_PORT=5001 +VIKUNJA_IP=10.0.10.27 +VIKUNJA_PORT=3456 + +# Bar Assistant (CT 103) +BAR_ASSISTANT_IP=10.0.10.40 +BAR_ASSISTANT_PORT=8080 + +# Minecraft Servers +MINECRAFT_FORGE_IP=10.0.10.41 +MINECRAFT_FORGE_PORT=25565 +MINECRAFT_STONEBLOCK_IP=10.0.10.42 +MINECRAFT_STONEBLOCK_PORT=25565 + +# Pterodactyl (CT 105/107) +PTERODACTYL_PANEL_IP=10.0.10.45 +PTERODACTYL_PANEL_PORT=80 +PTERODACTYL_WINGS_IP=10.0.10.46 +PTERODACTYL_WINGS_PORT=8080 + +# ============================================================================= +# OTHER HOSTS +# ============================================================================= +# OpenMediaVault (VM 400) +OMV_IP=10.0.10.5 +OMV_NFS_PATH=/export/backups + +# HOMELAB-COMMAND (Windows PC) +HOMELAB_COMMAND_IP=10.0.10.10 + +# Twingate (CT 101) +TWINGATE_IP=10.0.10.179 + +# ============================================================================= +# PUBLIC DOMAINS (Caddy reverse proxy) +# ============================================================================= +DOMAIN_BASE=nianticbooks.com +DOMAIN_PROXMOX=freddesk.nianticbooks.com +DOMAIN_HOME_ASSISTANT=bob.nianticbooks.com +DOMAIN_AUTHENTIK=auth.nianticbooks.com +DOMAIN_3D_PRINTER=ad5m.nianticbooks.com +DOMAIN_COCKTAILS=cocktails.nianticbooks.com +DOMAIN_VIKUNJA=tasks.nianticbooks.com + +# ============================================================================= +# TRUSTED PROXIES (for services behind WireGuard) +# ============================================================================= +TRUSTED_PROXIES=10.0.9.0/24,10.0.8.0/24,10.0.9.3 diff --git a/infrastructure/.gitignore b/infrastructure/.gitignore new file mode 100644 index 0000000..7d7699d --- /dev/null +++ b/infrastructure/.gitignore @@ -0,0 +1,50 @@ +# Sensitive Infrastructure Data +*.key +*.pem +*.crt +*.p12 +*.pfx + +# Environment and Configuration Files +.env +.env.local +*.conf +config.yaml +config.yml +secrets.yaml +secrets.yml + +# SSH Keys +id_rsa +id_ed25519 +*.pub + +# API Keys and Tokens +*api-key* +*token* +*secret* + +# Backup Files +*.bak +*.backup +*-backup.* + +# Completed Audits with Sensitive Data +*-completed.md +*-filled.md + +# OS Files +.DS_Store +Thumbs.db + +# Editor Files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Claude Code local settings (but allow commands to be shared) +.claude/* +!.claude/commands/ +mc_server/modpack/*.zip diff --git a/infrastructure/.gitmodules b/infrastructure/.gitmodules new file mode 100644 index 0000000..a360be8 --- /dev/null +++ b/infrastructure/.gitmodules @@ -0,0 +1,3 @@ +[submodule "claude-shared"] + path = claude-shared + url = https://github.com/FredN9MRQ/claude-workflows.git diff --git a/infrastructure/.mcp.json b/infrastructure/.mcp.json new file mode 100644 index 0000000..d2489cb --- /dev/null +++ b/infrastructure/.mcp.json @@ -0,0 +1,15 @@ +{ + "mcpServers": { + "n8n-mcp": { + "command": "cmd", + "args": ["/c", "npx", "-y", "n8n-mcp"], + "env": { + "MCP_MODE": "stdio", + "LOG_LEVEL": "error", + "DISABLE_CONSOLE_OUTPUT": "true", + "N8N_API_URL": "http://10.0.10.22:5678", + "N8N_API_KEY": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1ZTVjZTQ2Zi1iNmUyLTQyMGEtYmUzMC1iYzQzYThlMDA1YjMiLCJpc3MiOiJuOG4iLCJhdWQiOiJwdWJsaWMtYXBpIiwiaWF0IjoxNzY4MzUxNTgxfQ.IEInZiVdFy4KivDmcvYXlnrvMUr1H1krPyjLRxs_5d4" + } + } + } +} diff --git a/infrastructure/2025-12-10 231147.png b/infrastructure/2025-12-10 231147.png new file mode 100644 index 0000000..ac06247 Binary files /dev/null and b/infrastructure/2025-12-10 231147.png differ diff --git a/infrastructure/2025-12-10 231412.png b/infrastructure/2025-12-10 231412.png new file mode 100644 index 0000000..4d12bd8 Binary files /dev/null and b/infrastructure/2025-12-10 231412.png differ diff --git a/infrastructure/3D-PRINTING-QUICK-START.md b/infrastructure/3D-PRINTING-QUICK-START.md new file mode 100644 index 0000000..1ab8f1c --- /dev/null +++ b/infrastructure/3D-PRINTING-QUICK-START.md @@ -0,0 +1,153 @@ +# 3D Printing Setup - Quick Start Guide + +Quick reference for setting up Orca Slicer with shared profiles on your homelab. + +## First Time Setup (Run these commands) + +### 1. Mount the 3DPrinting Share + +```bash +# Mount the OMV share +sudo mount-3dprinting.sh + +# OR set up automatic mounting on boot +sudo setup-3dprinting-automount.sh +sudo mount /mnt/3DPrinting +``` + +Verify it worked: +```bash +ls -la /mnt/3DPrinting/3DPrinting/ +``` + +You should see: `profiles/`, `models/`, `gcode/`, `projects/` + +### 2. Install Orca Slicer + +```bash +# Install from the downloaded AppImage +sudo install-orca-slicer.sh +``` + +### 3. Launch Orca Slicer + +```bash +/opt/OrcaSlicer/orca-slicer.AppImage +``` + +Or search for "Orca Slicer" in your application menu. + +## Daily Usage + +### Slicing a Model + +1. Open Orca Slicer +2. File โ†’ Import โ†’ Select STL from `/mnt/3DPrinting/3DPrinting/models/` +3. Select profiles: + - Printer: AD5M + - Filament: PLA/PETG/etc + - Print: Quality level +4. Slice +5. Export gcode to `/mnt/3DPrinting/3DPrinting/gcode/queue/` +6. Transfer to printer and print! + +### Syncing Profiles + +**Get latest shared profiles:** +```bash +sync-orca-profiles.sh pull +``` + +**Share your updated profiles:** +```bash +sync-orca-profiles.sh push +``` + +**Check sync status:** +```bash +sync-orca-profiles.sh status +``` + +## File Locations + +### On OMV (10.0.10.5) +``` +/srv/dev-disk-by-uuid-1c893fab-9943-43df-8e24-3c9190869955/data/3DPrinting/ +โ”œโ”€โ”€ profiles/ # Shared Orca Slicer profiles +โ”œโ”€โ”€ models/ # STL files +โ”œโ”€โ”€ gcode/ # Sliced gcode files +โ””โ”€โ”€ projects/ # Work in progress +``` + +### On This Computer +``` +/mnt/3DPrinting/3DPrinting/ # Mounted share (same as above) +~/.config/OrcaSlicer/user/ # Local Orca Slicer profiles +``` + +## Troubleshooting + +### Share not mounted? +```bash +# Check if mounted +mount | grep 3DPrinting + +# If not mounted +sudo mount-3dprinting.sh +``` + +### Can't find profiles in Orca Slicer? +```bash +# Pull profiles from shared storage +sync-orca-profiles.sh pull + +# Restart Orca Slicer +``` + +### OMV server not accessible? +```bash +# Test connection +ping 10.0.10.5 + +# Check if SMB is running on OMV +ssh 10.0.10.5 "systemctl status smbd" +``` + +## Helper Scripts + +All scripts are in `~/.local/bin/`: + +| Script | Purpose | +|--------|---------| +| `mount-3dprinting.sh` | Mount the 3DPrinting share (requires sudo) | +| `setup-3dprinting-automount.sh` | Configure automatic mounting on boot | +| `install-orca-slicer.sh` | Install Orca Slicer from AppImage | +| `sync-orca-profiles.sh pull` | Download shared profiles | +| `sync-orca-profiles.sh push` | Upload your profiles to share | +| `sync-orca-profiles.sh status` | Check sync configuration | + +## Next Steps + +1. โœ… Mount share: `sudo mount-3dprinting.sh` +2. โœ… Install Orca Slicer: `sudo install-orca-slicer.sh` +3. ๐Ÿ”ฒ Launch Orca Slicer and configure AD5M printer profile +4. ๐Ÿ”ฒ Test slice a model +5. ๐Ÿ”ฒ Push your AD5M profiles: `sync-orca-profiles.sh push` +6. ๐Ÿ”ฒ Install Orca Slicer on other family computers +7. ๐Ÿ”ฒ On those computers: mount share, install Orca Slicer, pull profiles + +## Family Members: Getting Started + +If you're a family member setting up Orca Slicer on your computer: + +1. **Mount the network share** (ask Fred for help with this part) +2. **Install Orca Slicer** - Download from https://github.com/SoftFever/OrcaSlicer/releases/latest +3. **Get the shared profiles**: + ```bash + sync-orca-profiles.sh pull + ``` +4. **Start slicing!** All the AD5M profiles are ready to use + +## Documentation + +For complete setup details, see: [3D-PRINTING-SETUP.md](3D-PRINTING-SETUP.md) diff --git a/infrastructure/3D-PRINTING-SETUP.md b/infrastructure/3D-PRINTING-SETUP.md new file mode 100644 index 0000000..0ebd74a --- /dev/null +++ b/infrastructure/3D-PRINTING-SETUP.md @@ -0,0 +1,325 @@ +# 3D Printing - Shared Orca Slicer Setup + +This document describes the setup for sharing Orca Slicer profiles across multiple computers in the homelab, while keeping installations local for best performance. + +## Overview + +**Goal**: Family members can use Orca Slicer on their own computers with shared profiles for the AD5M printer, with files stored on OMV. + +**Approach**: +- Local Orca Slicer installations on each computer (best performance) +- Shared profiles stored on OMV NFS/SMB share +- Shared STL library and gcode output folder on OMV + +## OMV Shared Folder Structure + +Create the following structure on your OMV storage: + +``` +/srv/3DPrinting/ +โ”œโ”€โ”€ profiles/ # Shared Orca Slicer profiles +โ”‚ โ”œโ”€โ”€ filament/ # Filament profiles +โ”‚ โ”œโ”€โ”€ print/ # Print profiles (layer heights, speeds, etc.) +โ”‚ โ”œโ”€โ”€ printer/ # Printer profiles (AD5M config) +โ”‚ โ””โ”€โ”€ process/ # Process profiles +โ”œโ”€โ”€ models/ # STL files library +โ”‚ โ”œโ”€โ”€ functional/ +โ”‚ โ”œโ”€โ”€ decorative/ +โ”‚ โ””โ”€โ”€ repairs/ +โ”œโ”€โ”€ gcode/ # Sliced output ready to print +โ”‚ โ”œโ”€โ”€ queue/ # Ready to print +โ”‚ โ””โ”€โ”€ archive/ # Completed prints +โ””โ”€โ”€ projects/ # Work-in-progress projects +``` + +## OMV Setup Steps + +### 1. Create Shared Folder on OMV + +SSH into your OMV server (pve-storage or wherever OMV is running): + +```bash +# Create the directory structure +sudo mkdir -p /srv/3DPrinting/{profiles/{filament,print,printer,process},models/{functional,decorative,repairs},gcode/{queue,archive},projects} + +# Set permissions (adjust user/group as needed) +sudo chown -R fred:users /srv/3DPrinting +sudo chmod -R 775 /srv/3DPrinting +``` + +### 2. Create NFS Share in OMV + +Via OMV web interface: +1. Storage โ†’ Shared Folders โ†’ Create + - Name: `3DPrinting` + - Device: Your storage device + - Path: `/3DPrinting/` + - Permissions: fred (R/W), users (R/W) + +2. Services โ†’ NFS โ†’ Shares โ†’ Create + - Shared folder: `3DPrinting` + - Client: `10.0.10.0/24` (adjust to your network) + - Privilege: Read/Write + - Extra options: `rw,sync,no_subtree_check,no_root_squash` + +### 3. Mount on Client Computers + +Add to `/etc/fstab` on each client computer: + +```bash +# Replace with your OMV server IP +:/export/3DPrinting /mnt/3DPrinting nfs defaults,user,auto,noatime 0 0 +``` + +Mount the share: +```bash +sudo mkdir -p /mnt/3DPrinting +sudo mount /mnt/3DPrinting +``` + +Or use SMB/CIFS if preferred: +```bash +# Add to /etc/fstab +///3DPrinting /mnt/3DPrinting cifs credentials=/home/fred/.smbcredentials,uid=1000,gid=1000 0 0 +``` + +## Orca Slicer Installation + +### On Ubuntu/Debian (including this computer) + +1. Download the latest AppImage from GitHub: +```bash +cd ~/Downloads +wget https://github.com/SoftFever/OrcaSlicer/releases/latest/download/OrcaSlicer_Linux_V2.2.0.AppImage +chmod +x OrcaSlicer_Linux_*.AppImage +``` + +2. Move to a permanent location: +```bash +sudo mkdir -p /opt/OrcaSlicer +sudo mv OrcaSlicer_Linux_*.AppImage /opt/OrcaSlicer/orca-slicer.AppImage +``` + +3. Create desktop entry: +```bash +cat > ~/.local/share/applications/orca-slicer.desktop < -- bash -c 'command'" # Execute in CT +pct status # Check status +pct exec -- docker logs --tail 50 # View logs +pct exec -- docker restart # Restart +``` + +### Caddy (VPS) +```bash +ssh fred@66.63.182.168 "nano /etc/caddy/Caddyfile" # Edit +ssh fred@66.63.182.168 "sudo systemctl reload caddy" # Reload +ssh fred@66.63.182.168 "sudo journalctl -u caddy --tail 50" # Logs +``` + +### Backups +- PostgreSQL: Daily 2:00 AM โ†’ 10.0.10.5:/export/backups (7d/4w/3m retention) +- Proxmox: Daily 2:30 AM โ†’ OMV NFS +- Log: `/var/log/homelab-backup.log` + +--- + +## ACTIVE TODOS + +### High Priority +1. Configure Prometheus targets + Grafana dashboards +2. Remove deprecated VMs (Spoolman 10.0.10.71, Authelia 10.0.10.112) + +### Medium Priority +- DNS: omv.nianticbooks.home โ†’ 10.0.10.5 +- n8n service monitoring workflow (#4833) +- Authentik SSO integrations (Home Assistant, others) + +### Low Priority +- Tier 2/3 backups (off-site, cloud) +- Home Assistant HTTPS certificates + +--- + +## AVAILABLE IPs + +**Reserved blocks:** +- 10.0.10.6-9 (infrastructure) +- 10.0.10.11-12, 14, 16-19 (management) +- 10.0.10.28-29, 32-39, 43-44, 47-49 (utility) + +--- + +**Source:** C:/Users/Fred/projects/infrastructure/.claude/docs/ diff --git a/infrastructure/BLUEBUBBLES-SETUP.md b/infrastructure/BLUEBUBBLES-SETUP.md new file mode 100644 index 0000000..b4e5be7 --- /dev/null +++ b/infrastructure/BLUEBUBBLES-SETUP.md @@ -0,0 +1,267 @@ +# BlueBubbles Server Setup Guide + +**Last Updated:** 2026-01-31 +**Service:** BlueBubbles iMessage Server +**Host:** Fred's iMac (10.0.10.11 / 10.0.10.144) +**Version:** v1.9.9 +**Status:** In Setup + +--- + +## Overview + +BlueBubbles allows you to access iMessage on Android, Windows, and Linux devices by running a server on your Mac that forwards messages via WebSocket. + +**Architecture:** +``` +iMessage on iMac (10.0.10.11) + โ†“ +BlueBubbles Server (Port 1234) + โ†“ +Caddy VPS Proxy (bluebubbles.nianticbooks.com) + โ†“ +BlueBubbles Clients (Android/Web/Desktop) +``` + +--- + +## Installation Steps + +### Step 1: Download BlueBubbles Server + +**IMPORTANT:** You need to download this via Safari on your iMac due to GitHub's download mechanism. + +On your iMac: + +1. Open Safari and navigate to: + ``` + https://github.com/BlueBubblesApp/bluebubbles-server/releases/tag/v1.9.9 + ``` + +2. Scroll down to "Assets" section +3. Click on **`BlueBubbles-v1.9.9.dmg`** (NOT the `.zip` file) +4. The file will download to your ~/Downloads folder + +### Step 2: Install BlueBubbles Server + +1. Open the downloaded `.dmg` file +2. Drag BlueBubbles to your Applications folder +3. Open BlueBubbles from Applications +4. If you get a security warning: + - Go to System Settings โ†’ Privacy & Security + - Click "Open Anyway" for BlueBubbles + +### Step 3: Initial Configuration + +When BlueBubbles opens for the first time: + +1. **Grant Permissions:** + - Full Disk Access (required to read iMessage database) + - Accessibility (required for Private API features) + - Go to: System Settings โ†’ Privacy & Security โ†’ Full Disk Access + - Add BlueBubbles and enable it + +2. **Server Settings:** + - **Port:** 1234 (default, or choose your own) + - **Password:** Create a strong password (save it securely) + - **Server Name:** "Fred's BlueBubbles" (or any name you prefer) + +3. **Enable Private API** (Recommended): + - This enables advanced features like: + - Typing indicators + - Read receipts + - Message reactions + - Send messages as specific accounts + - Toggle "Enable Private API" in settings + - Follow the on-screen instructions to install the helper + +4. **Auto-Start Settings:** + - โœ… Enable "Start server on login" + - โœ… Enable "Start minimized" + - This ensures BlueBubbles runs automatically + +### Step 4: Configure Firewall (if enabled) + +If macOS Firewall is enabled on your iMac: + +1. Go to System Settings โ†’ Network โ†’ Firewall +2. Click "Options..." +3. Add BlueBubbles to allowed apps +4. OR allow incoming connections on port 1234 + +### Step 5: Test Local Access + +Before setting up external access, verify local functionality: + +1. Note your server URL shown in BlueBubbles (should be like `http://10.0.10.11:1234`) +2. Note your password +3. Install BlueBubbles client on your phone/another device +4. Connect using: + - **Server URL:** `http://10.0.10.11:1234` + - **Password:** [Your password] +5. Verify you can see your messages + +--- + +## External Access Setup (via Caddy VPS) + +Once local access works, we'll set up external access via your VPS. + +### Option 1: Use BlueBubbles Built-in Proxy (Easiest) + +BlueBubbles includes built-in support for Cloudflare Tunnel, Ngrok, and Zrok: + +1. In BlueBubbles settings, go to "Proxy Service" +2. Choose one of the free options: + - **Cloudflare Tunnel** (recommended - free, reliable) + - **Zrok** (free, open source) + - **Ngrok** (free tier available) +3. Follow the on-screen setup for your chosen service +4. Use the generated URL for remote connections + +### Option 2: Use Your Caddy VPS (More Control) + +Add to your Caddyfile on VPS (66.63.182.168): + +```caddyfile +bluebubbles.nianticbooks.com { + reverse_proxy 10.0.10.11:1234 { + header_up X-Forwarded-Host {host} + header_up X-Forwarded-Proto {scheme} + header_up X-Real-IP {remote_host} + header_up X-Forwarded-For {remote_host} + } +} +``` + +Then reload Caddy: +```bash +ssh fred@66.63.182.168 +sudo systemctl reload caddy +``` + +**Client Connection:** +- **Server URL:** `https://bluebubbles.nianticbooks.com` +- **Password:** [Your password] + +--- + +## Security Considerations + +1. **Use HTTPS:** Always use HTTPS for external connections (Caddy handles this automatically) +2. **Strong Password:** Use a unique, strong password for BlueBubbles +3. **Keep iMac Secure:** Since it has access to all your messages: + - Enable FileVault disk encryption + - Use a strong user password + - Keep macOS updated +4. **Network Security:** Your iMac is already on static IP 10.0.10.11, which is in the static range + +--- + +## Maintenance + +### Keep BlueBubbles Running + +BlueBubbles needs to run 24/7 to relay messages. To ensure it stays running: + +1. Set iMac to never sleep: + - System Settings โ†’ Energy Saver โ†’ Prevent automatic sleeping + - OR: Use "caffeinate" command to keep it awake + +2. Enable "Start server on login" in BlueBubbles settings + +3. Optionally: Set up monitoring in Uptime Kuma (10.0.10.26) + +### Updates + +Check for updates periodically: +- BlueBubbles โ†’ Settings โ†’ Check for Updates +- Or visit: https://github.com/BlueBubblesApp/bluebubbles-server/releases + +### Logs + +If you encounter issues: +- BlueBubbles โ†’ Settings โ†’ Logs +- Logs are stored in: `~/Library/Application Support/bluebubbles-server/` + +--- + +## Client Apps + +Download BlueBubbles clients for your devices: + +- **Android:** https://play.google.com/store/apps/details?id=com.bluebubbles.messaging +- **Web:** https://bluebubbles.app/web +- **Desktop (Windows/Linux):** https://github.com/BlueBubblesApp/bluebubbles-app/releases + +--- + +## Troubleshooting + +### Messages not syncing +- Verify Full Disk Access is granted +- Check iMessage is signed in and working on iMac +- Restart BlueBubbles server + +### Can't connect remotely +- Verify local connection works first (http://10.0.10.11:1234) +- Check Caddy configuration and reload +- Verify WireGuard tunnel is up +- Check firewall settings on iMac + +### Private API not working +- Ensure SIP (System Integrity Protection) is not blocking it +- Reinstall Private API helper from BlueBubbles settings +- Check macOS version compatibility + +### iMac goes to sleep +- Disable sleep in Energy Saver settings +- Check "Prevent automatic sleeping when display is off" +- Use "caffeinate" command to prevent sleep + +--- + +## Next Steps + +After completing this setup: + +1. โœ… Test local connection +2. โœ… Set up external access (choose Option 1 or 2) +3. โœ… Install client apps on your devices +4. โœ… Test sending/receiving messages remotely +5. โฌœ Set up monitoring in Uptime Kuma (optional) +6. โฌœ Document final configuration in SERVICES.md + +--- + +## Service Information + +**Service Details:** +- **Host:** Fred's iMac (Late 2013, 3.2GHz i5, 24GB RAM, macOS Sequoia) +- **IP Address (Ethernet):** 10.0.10.11 (configured, cable not connected) +- **IP Address (Wi-Fi):** 10.0.10.144 (currently active) +- **Port:** 1234 (default) +- **Version:** 1.9.9 +- **Auto-Start:** Enabled +- **Private API:** Enabled (recommended) + +**Public Access:** +- **Option 1:** Built-in proxy (Cloudflare/Ngrok/Zrok) +- **Option 2:** https://bluebubbles.nianticbooks.com (via Caddy VPS) + +**Status:** Setup in progress + +--- + +## References + +- GitHub: https://github.com/BlueBubblesApp/bluebubbles-server +- Documentation: https://bluebubbles.app/faq/ +- Web Client: https://bluebubbles.app/web +- Community: https://discord.gg/bluebubbles + +--- + +**Installation Date:** 2026-01-31 +**Installed By:** Fred +**Last Verified:** Pending initial setup diff --git a/infrastructure/BRAINSTORM.md b/infrastructure/BRAINSTORM.md new file mode 100644 index 0000000..f522677 --- /dev/null +++ b/infrastructure/BRAINSTORM.md @@ -0,0 +1,495 @@ +# Infrastructure Brainstorming Session + +**Date**: 2025-10-28 +**Status**: Planning Phase + +--- + +## Initial Claude Code Discovery + +I watched this video, https://youtu.be/MsQACpcuTkU?si=2h5VUlgtIcpLbP1v literally took his word as fact and subscribed to Claude Pro. I need to set this up on my 2013 Mac Pro Running Sequoia (using Open Core Legacy Patcher) please outline the steps and process for making this happen + +### Claude Code Interest - The /init Command + +Specifically Claude Code, as a clarification, I am most intrigued by the use of the /init command + +**Setup Requirements:** +- Homebrew installation +- Claude Code CLI tool +- API authentication (separate from Claude Pro subscription) +- Note: Claude Pro โ‰  API access (separate billing) + +--- + +## Infrastructure Expansion Plans + +### Current Environment + +**VPS:** +- 2 cores / 4GB RAM +- Running: Pangolin reverse proxy with Gerbil tunnels (WireGuard-based) +- Concern: RAM and CPU usage limits + +**Home Lab (Proxmox):** +- **DL380p**: 32 cores, 96GB RAM (main cluster node) +- **i5**: 8 cores, 8GB RAM (secondary cluster node) +- **OMV**: 12TB storage node + +**Development Machine:** +- Mac Pro 2013 running Sequoia (via Open Core Legacy Patcher) + +### Proposed New Services + +1. **RustDesk Server** - Self-hosted remote desktop +2. **n8n** - Workflow automation platform +3. **Authentik** - Single Sign-On (SSO) platform +4. **Obsidian Livesync** - Self-hosted note synchronization + +--- + +## Architecture Decision: Hybrid Approach + +### VPS (Lightweight Services Only) +- Pangolin reverse proxy (existing) +- Gerbil tunnels (existing, WireGuard-based) +- RustDesk relay server (hbbr) - ~30-50MB RAM for NAT traversal only + +**Reasoning**: Keep VPS lightweight to avoid resource constraints + +### DL380p Proxmox (Heavy Lifting) +- PostgreSQL (shared database server) +- Authentik SSO with WebAuthn support +- n8n workflow automation +- RustDesk ID server (hbbs) - handles registration and signaling +- Prometheus + Grafana monitoring +- Obsidian CouchDB sync server + +**Reasoning**: Abundant resources (32 cores, 96GB RAM) available for all services + +--- + +## Authentik SSO - Core Requirements + +### WebAuthn/FIDO2 Hardware Authentication + +**Critical Requirement**: Device-specific hardware 2FA + +**Supported Devices:** +- iPhone with Face ID (biometric authentication) +- Windows 11 laptop with Windows Hello (fingerprint/face/PIN) +- No YubiKey required (but supported if needed later) + +**Security Features:** +- Phishing-resistant (WebAuthn verifies domain) +- Each device has unique cryptographic key +- Keys stored in device secure enclave (iPhone) or TPM (Windows) +- Can revoke individual devices if lost/stolen +- TOTP as backup MFA method + +### Integration Targets + +**Priority 1 (Critical):** +- Proxmox VE (OpenID Connect) +- n8n (OAuth2) +- Pangolin admin dashboard (if supported) + +**Priority 2 (Nice to have):** +- Grafana (OAuth2) +- HomeAssistant (OAuth2) +- Any future services + +**SSO Policies:** +- External access (via Pangolin): WebAuthn REQUIRED +- Internal network access: WebAuthn preferred, TOTP acceptable +- Admin operations: Always require WebAuthn + +--- + +## Network Architecture + +### Flow Diagram +``` +Internet โ†’ VPS (Pangolin Reverse Proxy) + โ†“ + Gerbil Tunnel (WireGuard) + โ†“ + DL380p Proxmox Home Lab + โ†“ + Authentik SSO โ†โ†’ All Services + โ”œโ”€โ†’ n8n + โ”œโ”€โ†’ RustDesk (hbbs) + โ”œโ”€โ†’ Grafana + โ”œโ”€โ†’ Proxmox Web UI + โ””โ”€โ†’ HomeAssistant (future) +``` + +### Service Endpoints +- `auth.yourdomain.com` โ†’ Authentik SSO +- `n8n.yourdomain.com` โ†’ n8n workflows +- `grafana.yourdomain.com` โ†’ Monitoring dashboards +- `obsidian.yourdomain.com` โ†’ Note sync (CouchDB) + +--- + +## Implementation Strategy: 8 Phases + +### Phase 1: Planning & Preparation +- Document current infrastructure +- Make architecture decisions (LXC vs Docker, shared vs separate PostgreSQL) +- Create project structure with Claude Code +- Plan network layout and port assignments + +### Phase 2: Infrastructure Foundation on Proxmox +- Deploy PostgreSQL 15 (shared database server) +- Network and port planning +- Reserve static IPs for all services + +### Phase 3: Deploy Core Services on Proxmox +- Authentik SSO with WebAuthn/FIDO2 support +- n8n workflow automation +- RustDesk ID server (hbbs) + +### Phase 4: VPS Configuration +- RustDesk relay server (hbbr) - lightweight +- Update Pangolin reverse proxy routes +- DNS record creation +- SSL certificate management + +### Phase 5: SSO Integration & WebAuthn Enrollment +- Configure Authentik OAuth2/OIDC providers +- Integrate Proxmox with OpenID Connect +- Integrate n8n with OAuth2 +- Enroll all personal devices (iPhone, Windows laptop) +- Set up TOTP backup + +### Phase 6: Monitoring, Security & Hardening +- Deploy Prometheus + Grafana monitoring stack +- Security hardening (firewall rules, Fail2ban, SSL) +- WebAuthn policies and device management +- Configure alerts + +### Phase 7: Backup, Documentation & Testing +- Comprehensive backup solution to OMV (NFS) +- Complete infrastructure documentation +- Testing and validation procedures +- Disaster recovery drills + +### Phase 8: Future Integrations +- HomeAssistant integration with Authentik +- Obsidian Livesync deployment +- Additional services as needed + +--- + +## Resource Allocation Plan + +### Proxmox DL380p Services + +| Service | Cores | RAM | Storage | Purpose | +|---------|-------|-----|---------|---------| +| PostgreSQL | 2 | 4GB | 20GB | Shared database for all services | +| Authentik | 2 | 3GB | 30GB | SSO platform with WebAuthn | +| n8n | 4 | 4GB | 40GB | Workflow automation | +| RustDesk (hbbs) | 2 | 2GB | 10GB | Remote desktop ID server | +| Monitoring | 2 | 4GB | 50GB | Prometheus + Grafana | +| Obsidian Sync | 2 | 2GB | 50GB | CouchDB for note synchronization | +| **Total** | **14** | **19GB** | **200GB** | | +| **Available** | **18/32** | **77GB/96GB** | - | Still plenty of headroom! | + +### VPS Resource Usage + +| Service | Cores | RAM | Purpose | +|---------|-------|-----|---------| +| Pangolin | ~1 | ~2GB | Reverse proxy | +| Gerbil | ~0.5 | ~256MB | WireGuard tunnels | +| RustDesk (hbbr) | ~0.5 | ~128MB | NAT traversal relay | +| **Total** | **~2** | **~2.4GB** | | +| **Limit** | **2** | **4GB** | Within safe limits โœ… | + +--- + +## Obsidian Implementation Details + +### Why Obsidian for Infrastructure Documentation? +- Native markdown checkbox support +- Real-time sync across all devices (Mac, Windows, iPhone) +- Self-hosted sync (no subscription needed) +- Can store infrastructure checklist, notes, diagrams +- Works offline +- End-to-end encrypted + +### Obsidian Livesync Architecture +- CouchDB server on Proxmox (backend) +- Obsidian apps on all devices (clients) +- Self-hosted sync via Pangolin reverse proxy +- Database: `obsidian-vault` +- Backup to OMV storage + +### Device Setup +1. Mac Pro: Primary documentation device +2. Windows 11 Laptop: Access from work/travel +3. iPhone: Mobile access to infrastructure notes and checklists + +### Integration with Infrastructure Project +- Implementation checklist (190+ tasks) stored in Obsidian +- Real-time updates across devices as tasks are completed +- Can attach network diagrams, screenshots, configs +- Version history via CouchDB replication + +--- + +## Security Considerations + +### Authentication Layers +1. **Network Level**: Gerbil tunnel encryption (WireGuard) +2. **Application Level**: Authentik SSO with WebAuthn +3. **Device Level**: Hardware-based authentication (Face ID, Windows Hello) +4. **Backup Level**: TOTP authenticator app + +### Firewall Strategy +- VPS: Only expose Pangolin ports (80, 443, Gerbil tunnel port) +- Proxmox: Internal network only, no direct external access +- LXC containers: Isolated, only necessary inter-container communication +- Fail2ban on Authentik and VPS SSH + +### Backup Security +- Daily backups to OMV (12TB NFS storage) +- Weekly and monthly rotation +- PostgreSQL dumps (compressed) +- Authentik media and config backups +- n8n workflow backups (credentials encrypted) +- RustDesk encryption keys (CRITICAL) +- Grafana dashboards +- Off-site backup optional (cloud via rclone) + +### Certificate Management +- Let's Encrypt via Pangolin +- Automated renewal +- HSTS headers enabled +- TLS 1.3 enforcement + +--- + +## Development Approach: Claude Code Usage + +### Primary Use Cases +1. Generate complete deployment scripts for each service +2. Create LXC container configurations +3. Generate Docker Compose files +4. Create backup automation scripts +5. Generate comprehensive documentation +6. Create testing and validation scripts + +### Example /init Commands + +**PostgreSQL Deployment:** +``` +/init Create PostgreSQL 15 deployment for Proxmox LXC container with: +- Debian 12 base +- Separate databases for authentik, n8n, rustdesk, grafana +- Optimized for 4GB RAM +- Backup scripts to NFS mount +``` + +**Authentik with WebAuthn:** +``` +/init Create Authentik SSO server deployment for Proxmox LXC with WebAuthn/FIDO2 support: +- Docker Compose setup +- External PostgreSQL connection +- WebAuthn enrollment flows +- OAuth2/OIDC provider configurations +- Integration templates for Proxmox, n8n, Grafana +``` + +**Complete Infrastructure:** +``` +/init Create comprehensive project structure for self-hosted infrastructure: +- Folder organization for all services +- Deployment phase documentation +- Environment templates +- Backup automation +- Monitoring dashboards +- Security hardening checklists +``` + +--- + +## Timeline Estimate + +### Week 1: Foundation (Phases 1-3) +- Day 1-2: Planning and documentation +- Day 3-4: PostgreSQL and network setup +- Day 5-7: Deploy Authentik, n8n, RustDesk on Proxmox + +### Week 2: Integration (Phases 4-5) +- Day 1-2: VPS services and Pangolin configuration +- Day 3-5: SSO integration and WebAuthn enrollment +- Day 6-7: Testing and troubleshooting + +### Week 3: Finalization (Phases 6-7) +- Day 1-3: Monitoring, security hardening, backup automation +- Day 4-5: Complete documentation +- Day 6-7: Comprehensive testing and disaster recovery drill + +### Week 4+: Expansion (Phase 8) +- HomeAssistant integration +- Obsidian Livesync deployment +- Additional services as needed + +**Note**: This is a methodical, careful rollout. No rushing. Test each phase thoroughly before proceeding. + +--- + +## Success Metrics + +### Technical Metrics +- All services accessible externally via SSO +- WebAuthn works on all enrolled devices +- No single service exceeding allocated resources +- VPS CPU/RAM usage under control (<50% / <3GB) +- Backups running successfully (100% success rate) +- All monitoring dashboards populated with data +- Zero unplanned downtime during deployment + +### User Experience Metrics +- Single sign-on across all services +- Face ID / Windows Hello authentication works seamlessly +- No password fatigue (SSO handles everything) +- Mobile access to all services via Authentik +- Infrastructure documentation accessible from any device (Obsidian) +- Fast response times (<2s for service access) + +### Security Metrics +- All external access requires WebAuthn +- No default passwords remaining +- Fail2ban protecting critical services +- SSL certificates valid and auto-renewing +- Audit logging enabled in Authentik +- Regular backup verification (monthly) + +--- + +## Open Questions / Decisions Needed + +### To Decide Before Starting: +- [ ] Confirm domain names to use (auth.domain.com, n8n.domain.com, etc.) +- [ ] LXC containers vs Docker VMs? (Recommendation: LXC for efficiency) +- [ ] Shared PostgreSQL or separate instances? (Recommendation: Shared) +- [ ] Separate VLAN for services? (Recommendation: Yes, if possible) +- [ ] Let's Encrypt via Pangolin or internal CA? (Recommendation: Let's Encrypt) +- [ ] Off-site backup strategy? (Cloud, second location, etc.) + +### To Document During Setup: +- [ ] IP addresses assigned to each service +- [ ] Database credentials (store securely) +- [ ] OAuth Client IDs and secrets +- [ ] Authentik admin credentials +- [ ] RustDesk encryption keys (CRITICAL!) +- [ ] Backup schedule and retention +- [ ] Emergency access procedures + +--- + +## Lessons Learned / Notes + +### Why Hybrid Architecture? +- VPS is resource-constrained (2 cores / 4GB RAM) +- DL380p has abundant resources (32 cores / 96GB RAM) +- Gerbil tunnels already provide secure connectivity +- Minimizes VPS costs while maximizing home lab utilization +- Services stay responsive (no resource contention on VPS) + +### Why Authentik over Alternatives? +- **vs Keycloak**: Much lighter weight (Keycloak needs 1-2GB+ RAM) +- **vs Authelia**: More feature-complete, better app support +- Native WebAuthn/FIDO2 support +- Modern UI +- Active development +- Good documentation +- Self-hosted (privacy and control) + +### Why LXC Containers? +- More efficient than VMs (less overhead) +- Native Proxmox integration +- Easier backups and snapshots +- Better resource utilization +- Faster boot times +- Still provides isolation + +### Why Shared PostgreSQL? +- Single database server to manage +- Easier backups (one dump for all databases) +- Resource efficiency (connection pooling) +- Simpler monitoring +- Adequate for home lab scale +- Can migrate to separate instances later if needed + +--- + +## Reference Links + +### Tools & Services +- **Claude Code**: https://docs.claude.com/en/docs/claude-code +- **Authentik**: https://goauthentik.io/ +- **n8n**: https://n8n.io/ +- **RustDesk**: https://rustdesk.com/ +- **Obsidian**: https://obsidian.md/ +- **Prometheus**: https://prometheus.io/ +- **Grafana**: https://grafana.com/ + +### Documentation Created +- CLAUDE.md - Repository guidance for Claude Code +- RUNBOOK.md - Operational procedures +- DISASTER-RECOVERY.md - Recovery procedures +- SERVICES.md - Service configuration templates +- IMPROVEMENTS.md - Infrastructure recommendations +- MONITORING.md - Monitoring setup guide +- infrastructure-audit.md - Infrastructure audit checklist +- Infrastructure-Implementation-Checklist.md - Complete deployment checklist + +### Automation Scripts +- backup-proxmox.sh - VM/container backups +- backup-vps.sh - VPS configuration backups +- health-check.sh - Service health monitoring +- cert-check.sh - SSL certificate expiration +- tunnel-monitor.sh - Gerbil tunnel monitoring +- resource-report.sh - Weekly resource reports + +--- + +## Next Immediate Actions + +1. **Review and finalize architecture decisions** + - Confirm domain names + - Decide on LXC vs Docker + - Plan network/VLAN layout + +2. **Start with Claude Code project structure** + ```bash + cd ~/proxmox-infrastructure + claude + /init Create comprehensive project structure... + ``` + +3. **Fill out infrastructure audit checklist** + - Current VPS details + - Proxmox network configuration + - Available IP addresses + - DNS provider details + +4. **Set up Obsidian for documentation** + - Install on Mac Pro + - Import implementation checklist + - Begin checking off tasks as completed + +5. **Begin Phase 1: Planning & Preparation** + - Document current state + - Make final decisions + - Create project scaffolding + +--- + +**Status**: Ready to begin implementation! +**Excitement Level**: ๐Ÿš€๐Ÿš€๐Ÿš€ + +**Last Updated**: 2025-10-28 diff --git a/infrastructure/CA-DEPLOYMENT-SUMMARY.md b/infrastructure/CA-DEPLOYMENT-SUMMARY.md new file mode 100644 index 0000000..cea679e --- /dev/null +++ b/infrastructure/CA-DEPLOYMENT-SUMMARY.md @@ -0,0 +1,231 @@ +# CA Certificate Deployment Summary + +**Deployment Date:** 2026-01-25 +**Deployment Status:** โœ… Complete - Phase 1 + +## What Was Deployed + +### 1. Homelab Internal CA Root Certificate Distribution + +The internal CA root certificate from your Step-CA server (10.0.10.15, CT 115) has been installed on: + +#### LXC Containers +- โœ… CT 102 - PostgreSQL (10.0.10.20) +- โœ… CT 106 - n8n (10.0.10.22) +- โœ… CT 127 - Dockge (10.0.10.27) +- โœ… CT 128 - Uptime Kuma (10.0.10.26) +- โš ๏ธ CT 104 - Authentik (10.0.10.21) - Not running during deployment + +#### Proxmox Hosts +- โœ… main-pve (10.0.10.3) +- โœ… pve-router (10.0.10.2) +- โœ… pve-storage (10.0.10.4) + +#### VPS +- โœ… 66.63.182.168 (vps.nianticbooks.com) + +**Location:** `/usr/local/share/ca-certificates/homelab-ca.crt` on all systems + +### 2. Internal HTTPS Reverse Proxy Deployment + +**Service:** Caddy Internal Proxy +**Location:** Docker container on CT 127 (10.0.10.27) +**Container Name:** caddy-internal +**Configuration:** `/opt/caddy-internal/` on CT 127 + +#### Services Now Available via HTTPS + +All services are accessible at `https://.nianticbooks.home`: + +| Service | HTTPS URL | Backend Port | +|---------|-----------|--------------| +| Sonarr | https://sonarr.nianticbooks.home | 8989 | +| Radarr | https://radarr.nianticbooks.home | 7878 | +| Prowlarr | https://prowlarr.nianticbooks.home | 9696 | +| Bazarr | https://bazarr.nianticbooks.home | 6767 | +| Deluge | https://deluge.nianticbooks.home | 8112 | +| Calibre-Web | https://calibre.nianticbooks.home | 8083 | +| Vikunja | https://vikunja.nianticbooks.home | 3456 | +| Dockge | https://dockge.nianticbooks.home | 5001 | + +**Certificate Type:** Caddy Internal PKI (self-signed) +**Certificate Authority:** Caddy Local Authority - 2026 ECC Root + +## Client Configuration Required + +To access these services without certificate warnings, you need to install the Caddy Internal CA certificate on your client devices. + +### CA Certificate Location + +The Caddy internal root CA certificate is saved at: +- **Infrastructure Repo:** `~/projects/infrastructure/Caddy-Internal-Root-CA.crt` +- **On Server:** Extract with `docker exec caddy-internal cat /data/caddy/pki/authorities/local/root.crt` + +### Installation Instructions + +#### Windows +1. Download `Caddy-Internal-Root-CA.crt` from the infrastructure repo +2. Double-click the certificate file +3. Click "Install Certificate" +4. Select "Local Machine" (requires admin) +5. Choose "Place all certificates in the following store" +6. Click "Browse" and select "Trusted Root Certification Authorities" +7. Click "Next" and "Finish" + +#### Linux/WSL +```bash +sudo cp Caddy-Internal-Root-CA.crt /usr/local/share/ca-certificates/ +sudo update-ca-certificates +``` + +#### macOS +```bash +sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain Caddy-Internal-Root-CA.crt +``` + +#### Firefox (All Platforms) +Firefox uses its own certificate store: +1. Open Firefox Settings โ†’ Privacy & Security โ†’ Certificates โ†’ View Certificates +2. Click "Authorities" tab โ†’ "Import" +3. Select `Caddy-Internal-Root-CA.crt` +4. Check "Trust this CA to identify websites" +5. Click OK + +## DNS Configuration + +For the `.nianticbooks.home` domains to resolve, add to your DNS server (UCG Ultra DHCP/DNS): + +``` +sonarr.nianticbooks.home โ†’ 10.0.10.27 +radarr.nianticbooks.home โ†’ 10.0.10.27 +prowlarr.nianticbooks.home โ†’ 10.0.10.27 +bazarr.nianticbooks.home โ†’ 10.0.10.27 +deluge.nianticbooks.home โ†’ 10.0.10.27 +calibre.nianticbooks.home โ†’ 10.0.10.27 +vikunja.nianticbooks.home โ†’ 10.0.10.27 +dockge.nianticbooks.home โ†’ 10.0.10.27 +``` + +Or add a wildcard entry: +``` +*.nianticbooks.home โ†’ 10.0.10.27 +``` + +Alternatively, add to your local `/etc/hosts` (Linux/Mac) or `C:\Windows\System32\drivers\etc\hosts` (Windows): +``` +10.0.10.27 sonarr.nianticbooks.home radarr.nianticbooks.home prowlarr.nianticbooks.home bazarr.nianticbooks.home deluge.nianticbooks.home calibre.nianticbooks.home vikunja.nianticbooks.home dockge.nianticbooks.home +``` + +## Management Commands + +### View Caddy Logs +```bash +ssh root@10.0.10.3 "pct exec 127 -- docker logs caddy-internal -f" +``` + +### Restart Caddy +```bash +ssh root@10.0.10.3 "pct exec 127 -- docker restart caddy-internal" +``` + +### Update Caddyfile +```bash +# Edit on server +ssh root@10.0.10.3 +pct exec 127 -- bash +cd /opt/caddy-internal +nano Caddyfile +docker restart caddy-internal +``` + +### View Generated Certificates +```bash +ssh root@10.0.10.3 "pct exec 127 -- docker exec caddy-internal ls -la /data/caddy/certificates/local/" +``` + +## What's Still Needed (Phase 2) + +### Step-CA ACME Integration + +The current setup uses Caddy's internal PKI (self-signed certificates). For better integration with your existing Step-CA server, we need to: + +1. **Fix CA Server Certificate:** The Step-CA server certificate needs an IP SAN for 10.0.10.15 +2. **Configure ACME Client:** Update Caddy to use Step-CA ACME endpoint +3. **Trust Chain:** Ensure Caddy trusts the Step-CA root certificate + +**Benefit:** Single CA for the entire homelab instead of two separate CAs. + +### Services Still Needing SSL + +**Proxmox Hosts:** +- โœ… main-pve (10.0.10.3) - Already has SSL, needs CA-signed cert +- โœ… pve-router (10.0.10.2) - Already has SSL, needs CA-signed cert +- โœ… pve-storage (10.0.10.4) - Already has SSL, needs CA-signed cert + +**LXC Services:** +- โœ… Home Assistant (10.0.10.24) - Already has SSL, needs CA-signed cert +- โš ๏ธ n8n (10.0.10.22) - HTTP only +- โš ๏ธ Authentik (10.0.10.21) - HTTP only +- โš ๏ธ Grafana (10.0.10.25) - HTTP only + +**VPS Caddy:** +- Update VPS Caddy to use internal CA for public services +- Avoids "invalid certificate" warnings when accessing services remotely + +### Documentation + +- [ ] Update SERVICES.md with new HTTPS endpoints +- [ ] Create quick-start guide for new devices +- [ ] Add monitoring for certificate expiration + +## Scripts Created + +- **`scripts/deploy-ca-certificates.sh`** - Deploys homelab CA root to all containers +- **`scripts/setup-internal-caddy.sh`** - Interactive Caddy deployment (not used - manual deployment preferred) + +## Troubleshooting + +### Certificate Warnings Still Appear + +1. Verify CA certificate is installed on client device +2. Check that DNS resolves to 10.0.10.27 +3. Ensure you're using `https://` (not `http://`) +4. Clear browser cache and restart browser + +### Service Not Accessible + +1. Check Caddy is running: `docker ps | grep caddy-internal` +2. Check Caddy logs: `docker logs caddy-internal` +3. Verify backend service is running: `docker ps` or `systemctl status ` +4. Check firewall rules on CT 127 + +### Connection Refused + +- Caddy listens on port 443 only (no port 80) +- Ensure you're using HTTPS URLs +- Verify Caddy container is in `host` network mode + +## Security Considerations + +**Current State:** +- โœ… All internal traffic encrypted +- โœ… CA certificates properly distributed +- โš ๏ธ Using Caddy internal PKI (self-signed) instead of Step-CA + +**Recommendations:** +- Install CA certificate on all client devices immediately +- Do NOT expose Caddy internal proxy ports publicly (internal use only) +- Regularly update Caddy container for security patches + +## Next Steps + +1. **Immediate:** Install Caddy CA certificate on your primary devices +2. **Short-term:** Add DNS entries or hosts file entries +3. **Medium-term:** Migrate from Caddy internal PKI to Step-CA ACME +4. **Long-term:** Add remaining services (n8n, Authentik, Grafana) to HTTPS + +--- + +**Deployment Completed By:** Fred (with Claude Code) +**Last Updated:** 2026-01-25 +**Status:** โœ… Phase 1 Complete - Services accessible via HTTPS with self-signed certificates diff --git a/infrastructure/CA-WORK-IN-PROGRESS.md b/infrastructure/CA-WORK-IN-PROGRESS.md new file mode 100644 index 0000000..ca4735b --- /dev/null +++ b/infrastructure/CA-WORK-IN-PROGRESS.md @@ -0,0 +1,153 @@ +# CA Integration Work - Status & Resume Guide + +**Last Updated:** 2026-01-25 +**Status:** Phase 1 Complete โœ… - Ready for Phase 2 + +## Quick Summary + +You were concerned that your internal CA server (10.0.10.15, CT 115) wasn't being used by services, causing certificate warnings when accessing them remotely. We've completed Phase 1 of fixing this. + +## What's Been Done โœ… + +### Phase 1: Internal HTTPS (Complete) +- โœ… Distributed homelab CA root cert to all containers, Proxmox hosts, and VPS +- โœ… Deployed Caddy reverse proxy on CT 127 with SSL termination +- โœ… All Docker services (Sonarr, Radarr, Prowlarr, Bazarr, Deluge, Calibre, Vikunja, Dockge) now have HTTPS + +**Key Files:** +- [CA-DEPLOYMENT-SUMMARY.md](CA-DEPLOYMENT-SUMMARY.md) - Complete documentation +- [scripts/deploy-ca-certificates.sh](scripts/deploy-ca-certificates.sh) - Automation script +- Caddy config: `/opt/caddy-internal/Caddyfile` on CT 127 + +**Services:** All accessible at `https://.nianticbooks.home` + +**Caddy Container:** +- Running on CT 127 (10.0.10.27) +- Container name: `caddy-internal` +- Listens on port 443 (HTTPS only) +- Uses Caddy internal PKI (self-signed certificates) + +## What You Still Need To Do + +**Immediate (to remove browser warnings):** +1. Install CA certificate on your devices: `Caddy-Internal-Root-CA.crt` (in this directory) + - See CA-DEPLOYMENT-SUMMARY.md for platform-specific instructions +2. Add DNS entries or edit hosts file: + ``` + 10.0.10.27 sonarr.nianticbooks.home radarr.nianticbooks.home prowlarr.nianticbooks.home ... + ``` + +## What's Still Needed (Phase 2) + +### High Priority + +1. **Migrate to Step-CA ACME** (instead of Caddy internal PKI) + - Problem: CA server cert at 10.0.10.15 doesn't have IP SAN + - Solution: Regenerate CA server cert with IP SAN, or use DNS name + - Benefit: Single CA for entire homelab + +2. **Add SSL to remaining services:** + - Home Assistant (10.0.10.24) - has SSL, needs CA-signed cert + - Proxmox hosts (10.0.10.2, 10.0.10.3, 10.0.10.4) - have SSL, need CA-signed certs + - n8n (10.0.10.22) - HTTP only + - Authentik (10.0.10.21) - HTTP only + - Grafana (10.0.10.25) - HTTP only + +3. **Update VPS Caddy:** + - Configure VPS Caddy to use internal CA for public services + - Fixes cert warnings when accessing services from outside network + +### Lower Priority + +- Update SERVICES.md with new HTTPS endpoints +- Set up certificate expiration monitoring +- Add more services as needed + +## How To Resume This Work + +**To continue CA integration:** +```bash +cd ~/projects/infrastructure +# Review current state +cat CA-DEPLOYMENT-SUMMARY.md +cat CA-WORK-IN-PROGRESS.md # This file + +# Check Caddy status +ssh root@10.0.10.3 "pct exec 127 -- docker logs caddy-internal" + +# Continue with Phase 2 tasks above +``` + +**To modify Caddy configuration:** +```bash +ssh root@10.0.10.3 +pct exec 127 -- bash +cd /opt/caddy-internal +nano Caddyfile +docker restart caddy-internal +``` + +**To add more services:** +1. Edit `/opt/caddy-internal/Caddyfile` on CT 127 +2. Add new service block (see existing examples) +3. Restart: `docker restart caddy-internal` +4. Add DNS entry or hosts file entry + +## Key Locations + +**CA Certificates:** +- Homelab CA root: `/usr/local/share/ca-certificates/homelab-ca.crt` (on all systems) +- Caddy internal CA: Extract with `docker exec caddy-internal cat /data/caddy/pki/authorities/local/root.crt` +- Step-CA root: `/etc/step-ca/.step/certs/root_ca.crt` on CT 115 + +**Caddy Configuration:** +- Config directory: `/opt/caddy-internal/` on CT 127 +- Caddyfile: `/opt/caddy-internal/Caddyfile` +- Docker compose: `/opt/caddy-internal/docker-compose.yml` +- Certificate storage: Inside container at `/data/caddy/certificates/local/` + +**Services:** +- CA Server: 10.0.10.15 (CT 115) - ACME endpoint: https://10.0.10.15:8443/acme/acme/directory +- Caddy proxy: 10.0.10.27 (CT 127) - Port 443 + +## Problem Context (Why We Did This) + +**Original Issue:** +- You have many Docker services with web UIs +- They were HTTP only, causing download/connection issues +- When accessed remotely through VPS Caddy, certificate warnings appeared +- Your internal CA server wasn't being utilized by services + +**Solution Implemented:** +- Deployed SSL reverse proxy for all internal services +- Distributed CA certificates to trust the proxy +- Now all services have HTTPS with valid certificates (once CA cert installed on clients) + +## Commands Reference + +**View Caddy logs:** +```bash +ssh root@10.0.10.3 "pct exec 127 -- docker logs caddy-internal -f" +``` + +**Restart Caddy:** +```bash +ssh root@10.0.10.3 "pct exec 127 -- docker restart caddy-internal" +``` + +**Check listening ports:** +```bash +ssh root@10.0.10.3 "pct exec 127 -- ss -tlnp | grep caddy" +``` + +**Redeploy CA certificates (if needed):** +```bash +cd ~/projects/infrastructure +./scripts/deploy-ca-certificates.sh +``` + +--- + +**Git Commit:** `2418b48` - Deploy CA certificates and internal HTTPS reverse proxy +**Branch:** master +**Files Modified:** CA-DEPLOYMENT-SUMMARY.md, scripts/deploy-ca-certificates.sh, scripts/setup-internal-caddy.sh diff --git a/infrastructure/CLAUDE.md b/infrastructure/CLAUDE.md new file mode 100644 index 0000000..f8fb3b1 --- /dev/null +++ b/infrastructure/CLAUDE.md @@ -0,0 +1,162 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Quick Reference + +This is an infrastructure documentation and automation repository for a self-hosted home lab environment. + +**When you need specific information, refer to these specialized docs:** + +### Core Architecture + +- [.claude/docs/ARCHITECTURE.md](.claude/docs/ARCHITECTURE.md) - Infrastructure components, network config, WireGuard VPN +- [.claude/docs/SERVICES.md](.claude/docs/SERVICES.md) - Deployed services, planned deployments, service-specific details +- [.claude/docs/IP-QUICK-REF.md](.claude/docs/IP-QUICK-REF.md) - Essential IP addresses (< 20 most-used IPs) + +### Workflows & Operations + +- [.claude/docs/COMMON-TASKS.md](.claude/docs/COMMON-TASKS.md) - SSH access, Caddy management, git workflow, script deployment +- [.claude/docs/HOME-ASSISTANT.md](.claude/docs/HOME-ASSISTANT.md) - HA config management, sync workflows, integrations +- [.claude/docs/CERTIFICATES.md](.claude/docs/CERTIFICATES.md) - Step-CA setup, ACME provisioner, SSL configuration + +### Detailed References (load when needed) + +- `IP-ALLOCATION.md` - Complete network IP allocation (10.0.10.0/24) - **SOURCE OF TRUTH** +- `infrastructure-audit.md` - Current infrastructure state, running containers, service details +- `SERVICES.md` - Comprehensive service documentation with startup/health check commands +- `RUNBOOK.md` - Operational procedures +- `scripts/README.md` - Script usage and automation +- `guides/HOMELAB-BACKUP-STRATEGY.md` - Backup system architecture + +## Essential Quick Facts + +**Primary Infrastructure:** + +- VPS: fred@66.63.182.168 (Caddy reverse proxy) +- Gaming VPS: ubuntu@51.222.12.162 (WireGuard server, deadeyeg4ming.vip) +- Proxmox: root@10.0.10.3 (main-pve), root@10.0.10.2 (pve-router), root@10.0.10.4 (pve-storage) +- Network: 10.0.10.0/24 (DHCP: .50-.254, Static: .1-.49) + +**Critical IPs:** + +- 10.0.10.1 - UCG Ultra (gateway) +- 10.0.10.3 - main-pve (primary Proxmox) +- 10.0.10.10 - HOMELAB-COMMAND (Gaming PC, Windows 11, Claude Code host) +- 10.0.10.24 - Home Assistant + +**WireGuard Tunnel:** + +- Tunnel: 10.0.9.0/24 (Gaming VPS .1, UCG Ultra .2, VPS proxy IP .3) +- Endpoint: 51.222.12.162:51820 + +## Documentation Structure + +**Living Docs:** infrastructure-audit.md, IP-ALLOCATION.md, IMPROVEMENTS.md, MORNING-REMINDER.md + +**Reference Guides:** BRAINSTORM.md, RUNBOOK.md, DISASTER-RECOVERY.md, MONITORING.md + +**Automation:** scripts/README.md (all scripts require customization, use --dry-run) + +## Key Constraints + +- VPS: 2 CPU / 4GB RAM - lightweight services only +- Proxmox storage: `local` only (never `local-lvm`) +- SSH: Always use key-based auth +- Scripts: Test with `--dry-run` before production + +## Important Service Patterns + +### Internal HTTPS Access (Caddy Internal Proxy) + +Services on CT 127 (Dockge) use Caddy Internal Proxy for HTTPS with self-signed certificates: + +- **Location:** CT 127 (10.0.10.27) +- **Config:** `/opt/caddy-internal/Caddyfile` +- **Reload:** `ssh root@10.0.10.3 "pct exec 127 -- docker exec caddy-internal caddy reload --config /etc/caddy/Caddyfile"` +- **CA Certificate:** Extract with `pct exec 127 -- docker exec caddy-internal cat /data/caddy/pki/authorities/local/root.crt` + +**Internal domains pattern:** `https://.nianticbooks.home` โ†’ `10.0.10.27:` + +To enable HTTPS access: +1. Extract CA cert from Caddy Internal +2. Install on client devices (Windows: Trusted Root Certification Authorities) +3. Add entries to `C:\Windows\System32\drivers\etc\hosts` pointing to 10.0.10.27 + +**TODO:** Dockge container (CT 127) is not configured with the internal CA certificate. This needs to be addressed to allow Dockge UI to trust internal services using Caddy Internal PKI certificates. + +### Public HTTPS Access (via VPS Caddy) + +Services exposed publicly use Caddy on VPS (66.63.182.168): + +- **Config:** `/etc/caddy/Caddyfile` on VPS +- **Reload:** `ssh fred@66.63.182.168 "sudo systemctl reload caddy"` +- **Certificates:** Automatic Let's Encrypt via ACME +- **Traffic Flow:** Internet โ†’ VPS Caddy โ†’ WireGuard tunnel (10.0.9.3) โ†’ Gaming VPS (10.0.9.1) โ†’ Home network (10.0.10.x) + +**Current routing limitation:** WireGuard tunnel between Gaming VPS and home network has routing issues. Services work through port forwarding (DNAT) but general HTTP proxying times out. For new services requiring public access, prefer internal-only setup until routing is fixed. + +### LXC Container Management + +**Execute commands in containers:** +```bash +ssh root@10.0.10.3 "pct exec -- " +``` + +**Common container IDs:** +- CT 127 (Dockge): Media stack, internal proxy, Docker Compose management +- CT 106 (n8n): Workflow automation +- CT 102 (PostgreSQL): Shared database +- CT 121 (Authentik): SSO/authentication +- CT 128 (Uptime Kuma): Monitoring + +**Docker in LXC:** Dockge (CT 127) runs Docker. Access containers: +```bash +pct exec 127 -- docker ps +pct exec 127 -- docker logs +pct exec 127 -- docker restart +``` + +## Network Troubleshooting + +### WireGuard Tunnel Status + +**Gaming VPS acts as hub:** +- 10.0.9.1 (Gaming VPS server) +- 10.0.9.2 (UCG Ultra client - gateway to home network) +- 10.0.9.3 (Old VPS proxy IP) + +**Check tunnel:** +```bash +# From Gaming VPS +ssh ubuntu@51.222.12.162 "sudo wg show" + +# From old VPS +ssh fred@66.63.182.168 "sudo wg show" +``` + +**Known issue:** Gaming VPS cannot currently route traffic between WireGuard peers (10.0.9.3 โ†’ home network 10.0.10.x). Handshakes work but ICMP/HTTP traffic times out. UCG Ultra firewall allows traffic; issue is likely routing configuration on Gaming VPS. + +### DNS Resolution for Internal Services + +Internal `.home` domains require hosts file entries: +- Windows: `C:\Windows\System32\drivers\etc\hosts` +- Linux/Mac: `/etc/hosts` + +Format: `10.0.10.27 .nianticbooks.home` + +## Documentation Maintenance + +**Living documents (update frequently):** +- `IP-ALLOCATION.md` - Network IP assignments (source of truth) +- `infrastructure-audit.md` - Current infrastructure state +- `IMPROVEMENTS.md` - Planned enhancements +- `MORNING-REMINDER.md` - Daily workflow checklist + +**When adding new services:** +1. Reserve IP in `IP-ALLOCATION.md` +2. Document in `SERVICES.md` with startup/health check commands +3. Update `infrastructure-audit.md` with deployment date +4. Add to relevant .claude/docs files +5. If using internal HTTPS: add to Caddy Internal config +6. If using public HTTPS: add to VPS Caddy config diff --git a/infrastructure/DISASTER-RECOVERY.md b/infrastructure/DISASTER-RECOVERY.md new file mode 100644 index 0000000..21a347f --- /dev/null +++ b/infrastructure/DISASTER-RECOVERY.md @@ -0,0 +1,456 @@ +# Disaster Recovery Plan + +This document outlines procedures for recovering from various disaster scenarios affecting your infrastructure. + +## Table of Contents +- [Emergency Contact Information](#emergency-contact-information) +- [Recovery Time Objectives](#recovery-time-objectives) +- [Backup Locations](#backup-locations) +- [Disaster Scenarios](#disaster-scenarios) +- [Recovery Procedures](#recovery-procedures) +- [Post-Recovery Checklist](#post-recovery-checklist) + +--- + +## Emergency Contact Information + +### Primary Contacts +| Role | Name | Phone | Email | Availability | +|------|------|-------|-------|--------------| +| Infrastructure Owner | _____________ | _____________ | _____________ | 24/7 | +| Network Admin | _____________ | _____________ | _____________ | Business Hours | +| Backup Contact | _____________ | _____________ | _____________ | 24/7 | + +### Service Provider Contacts +| Provider | Service | Support Number | Account ID | Notes | +|----------|---------|----------------|------------|-------| +| VPS Provider | _____________ | _____________ | _____________ | _____________ | +| DNS Provider | _____________ | _____________ | _____________ | _____________ | +| Domain Registrar | _____________ | _____________ | _____________ | _____________ | +| ISP (Home Lab) | _____________ | _____________ | _____________ | _____________ | + +--- + +## Recovery Time Objectives + +Define acceptable downtime for each service tier: + +| Tier | Service Type | RTO (Recovery Time Objective) | RPO (Recovery Point Objective) | +|------|-------------|------------------------------|--------------------------------| +| Critical | Public-facing services, Authentication | 1 hour | 15 minutes | +| Important | Internal services, Databases | 4 hours | 1 hour | +| Standard | Development, Testing | 24 hours | 24 hours | +| Low Priority | Monitoring, Logging | 48 hours | 24 hours | + +--- + +## Backup Locations + +### Primary Backup Location +- **Location**: _____________ (e.g., OMV storage node, external drive) +- **Path**: _____________ +- **Retention**: _____________ +- **Access Method**: _____________ + +### Secondary Backup Location (Off-site) +- **Location**: _____________ (e.g., Cloud storage, remote server) +- **Path**: _____________ +- **Retention**: _____________ +- **Access Method**: _____________ + +### Backup Schedule +- **Proxmox VMs/Containers**: Daily at _____ +- **Configuration Files**: Weekly on _____ +- **Critical Data**: Hourly/Daily +- **Off-site Sync**: Daily/Weekly + +### Critical Items to Backup +- [ ] Proxmox VM/Container configurations and disks +- [ ] Pangolin reverse proxy configurations +- [ ] Gerbil tunnel configurations and keys +- [ ] SSL/TLS certificates and keys +- [ ] SSH keys and authorized_keys files +- [ ] Network configuration files +- [ ] DNS zone files (if self-hosted) +- [ ] Database dumps +- [ ] Application data and configurations +- [ ] Documentation and credentials (encrypted) + +--- + +## Disaster Scenarios + +### Scenario 1: VPS Complete Failure + +**Impact**: All public-facing services down, no external access to home lab services + +**Recovery Procedure**: +1. **Immediate Actions (0-15 minutes)** + - Verify VPS is actually down (ping, SSH, web checks) + - Contact VPS provider support + - Check VPS provider status page + - Notify users if necessary + +2. **Short-term Mitigation (15-60 minutes)** + - If hardware failure, request provider rebuild + - If account issue, resolve with provider + - Consider spinning up temporary VPS with another provider + +3. **VPS Rebuild (1-4 hours)** + ```bash + # On new VPS: + + # 1. Update system + sudo apt update && sudo apt upgrade -y + + # 2. Install Pangolin + # [Installation commands] + + # 3. Restore Pangolin configuration from backup + scp backup-server:/backups/pangolin-config.tar.gz . + sudo tar -xzf pangolin-config.tar.gz -C / + + # 4. Install Gerbil server + # [Installation commands] + + # 5. Restore Gerbil configuration + scp backup-server:/backups/gerbil-config.tar.gz . + sudo tar -xzf gerbil-config.tar.gz -C / + + # 6. Restore SSL certificates + sudo tar -xzf ssl-certs-backup.tar.gz -C /etc/letsencrypt/ + + # 7. Configure firewall + sudo ufw allow 22/tcp + sudo ufw allow 80/tcp + sudo ufw allow 443/tcp + sudo ufw allow [GERBIL_PORT]/tcp + sudo ufw enable + + # 8. Start services + sudo systemctl enable --now pangolin + sudo systemctl enable --now gerbil + + # 9. Update DNS A record to new VPS IP + # [DNS provider steps] + + # 10. Reconnect Gerbil tunnels from home lab + # [See Gerbil reconnection below] + ``` + +4. **Verification** + - Test all public routes + - Verify Gerbil tunnels are connected + - Check SSL certificates are valid + - Monitor logs for errors + +--- + +### Scenario 2: Home Lab Network Outage + +**Impact**: All home lab services unreachable, Gerbil tunnels down + +**Recovery Procedure**: +1. **Immediate Actions (0-15 minutes)** + - Check router/modem status + - Verify ISP is not having outage + - Check physical connections + - Reboot router/modem if necessary + +2. **ISP Outage (Variable duration)** + - Contact ISP support + - Consider failover to mobile hotspot if critical + - Notify users of expected downtime + +3. **Restore Gerbil Tunnels** + ```bash + # On each home lab machine with tunnels: + + # 1. Verify local services are running + systemctl status [service-name] + + # 2. Test VPS connectivity + ping [VPS_IP] + + # 3. Restart Gerbil tunnels + sudo systemctl restart gerbil-tunnel-* + + # 4. Verify tunnels are connected + gerbil status + + # 5. Check logs for errors + journalctl -u gerbil-tunnel-* -n 50 + ``` + +--- + +### Scenario 3: Proxmox Node Failure (DL380p or i5) + +**Impact**: All VMs/containers on failed node are down + +**Recovery Procedure**: +1. **Immediate Actions (0-30 minutes)** + - Identify which node has failed + - Determine cause (power, hardware, network) + - Check if other cluster nodes are healthy + +2. **If Node Can Be Recovered** + ```bash + # Try to boot node + # If successful, check cluster status: + pvecm status + + # Check VM/Container status + qm list + pct list + + # Start critical VMs/Containers + qm start VMID + pct start CTID + ``` + +3. **If Node Cannot Be Recovered - Migrate Services** + ```bash + # On working node: + + # 1. Check available resources + pvesh get /nodes/NODE/status + + # 2. Restore VMs from backup to working node + qmrestore /path/to/backup/vzdump-qemu-VMID.vma.zst NEW_VMID --storage local-lvm + + # 3. Restore containers from backup + pct restore NEW_CTID /path/to/backup/vzdump-lxc-CTID.tar.zst --storage local-lvm + + # 4. Start restored VMs/containers + qm start NEW_VMID + pct start NEW_CTID + + # 5. Update internal DNS/documentation with new IPs if changed + ``` + +4. **Resource Constraints** + - If insufficient resources on remaining node: + - Prioritize critical services only + - Consider scaling down VM resources temporarily + - Plan for hardware replacement/repair + +--- + +### Scenario 4: Storage Node (OMV) Failure + +**Impact**: Shared storage unavailable, backups inaccessible, data loss risk + +**Recovery Procedure**: +1. **Immediate Actions (0-30 minutes)** + - Verify storage node is down + - Check if disks are healthy (if node boots) + - Identify affected services using shared storage + +2. **If Disk Failure** + - Check RAID status (if configured) + - Replace failed disk + - Rebuild RAID array + - Restore from off-site backup if necessary + +3. **If Complete Storage Loss** + ```bash + # 1. Rebuild OMV on new hardware/disks + # [OMV installation] + + # 2. Configure network shares + # [NFS/CIFS setup] + + # 3. Restore data from off-site backup + rsync -avz backup-location:/backups/ /mnt/storage/ + + # 4. Remount shares on Proxmox nodes + # Update /etc/fstab on each node + mount -a + + # 5. Verify Proxmox can access storage + pvesm status + ``` + +--- + +### Scenario 5: DNS Provider Failure + +**Impact**: Domain not resolving, all services unreachable by domain name + +**Recovery Procedure**: +1. **Immediate Actions (0-15 minutes)** + - Check DNS provider status page + - Test DNS resolution: `nslookup domain.com` + - Verify it's provider issue, not configuration + +2. **Short-term Mitigation (15-60 minutes)** + - Share direct IP addresses with users temporarily + - Set up temporary DNS using Cloudflare (free tier) + +3. **Migrate to New DNS Provider** + ```bash + # 1. Export zone file from old provider (if possible) + + # 2. Create account with new DNS provider + + # 3. Import zone file or manually create records: + # A record: domain.com -> VPS_IP + # A record: *.domain.com -> VPS_IP (if using wildcard) + # Other records as needed + + # 4. Update nameservers at domain registrar + # (Propagation takes 24-48 hours) + + # 5. Monitor DNS propagation + dig domain.com @8.8.8.8 + ``` + +--- + +### Scenario 6: Complete Data Center Loss (Home Lab) + +**Impact**: All home lab infrastructure destroyed (fire, flood, etc.) + +**Recovery Procedure**: +1. **Immediate Actions** + - Ensure safety of personnel + - Contact insurance provider + - Assess extent of damage + - Secure remaining equipment + +2. **Short-term (Services that must continue)** + - Move critical services to VPS temporarily + - Use cloud providers for temporary hosting + - Restore from off-site backups + +3. **Long-term (Infrastructure Rebuild)** + - Procure replacement hardware + - Rebuild Proxmox cluster + - Restore VMs/containers from off-site backups + - Reconfigure network + - Re-establish Gerbil tunnels + - Full testing and verification + +--- + +## Recovery Procedures + +### General Recovery Steps + +1. **Assess the Situation** + - Identify what has failed + - Determine scope of impact + - Estimate recovery time + +2. **Communicate** + - Notify affected users + - Update status page if available + - Keep stakeholders informed + +3. **Prioritize** + - Focus on critical services first + - Use RTO/RPO objectives + - Document decisions + +4. **Execute Recovery** + - Follow specific scenario procedures + - Document all actions taken + - Keep logs of commands executed + +5. **Verify** + - Test all restored services + - Check data integrity + - Monitor for issues + +6. **Document** + - Record what happened + - Document what worked/didn't work + - Update this document with lessons learned + +--- + +## Post-Recovery Checklist + +After any disaster recovery, complete the following: + +### Immediate Post-Recovery (0-24 hours) +- [ ] All critical services are operational +- [ ] All services are monitored +- [ ] Temporary workarounds documented +- [ ] Incident logged with timeline + +### Short-term (1-7 days) +- [ ] All services fully restored +- [ ] Performance is normal +- [ ] Backups are running +- [ ] Security review completed +- [ ] Post-mortem meeting scheduled + +### Long-term (1-4 weeks) +- [ ] Post-mortem completed +- [ ] Lessons learned documented +- [ ] Disaster recovery plan updated +- [ ] Preventive measures implemented +- [ ] Training updated if needed +- [ ] Backup/monitoring improvements made + +--- + +## Post-Mortem Template + +After each disaster recovery event, complete a post-mortem: + +**Incident Date**: _____________ +**Recovery Completed**: _____________ +**Total Downtime**: _____________ + +### What Happened? +[Detailed description of the incident] + +### Timeline +| Time | Event | +|------|-------| +| _____ | _____ | +| _____ | _____ | + +### Root Cause +[What caused the failure?] + +### What Went Well? +- +- + +### What Went Poorly? +- +- + +### Action Items +| Action | Owner | Due Date | Status | +|--------|-------|----------|--------| +| _______ | _____ | ________ | ______ | + +### Improvements to This Plan +[What should be updated in the disaster recovery plan?] + +--- + +## Testing Schedule + +Regular disaster recovery testing ensures procedures work when needed: + +| Test Type | Frequency | Last Test | Next Test | Status | +|-----------|-----------|-----------|-----------|--------| +| Backup restore test | Quarterly | _________ | _________ | ______ | +| VPS failover drill | Semi-annually | _________ | _________ | ______ | +| Node failure simulation | Annually | _________ | _________ | ______ | +| Full DR scenario | Annually | _________ | _________ | ______ | + +--- + +## Document Maintenance + +**Last Updated**: _____________ +**Updated By**: _____________ +**Next Review Date**: _____________ +**Version**: 1.0 diff --git a/infrastructure/DNS-OVER-TLS-SETUP.md b/infrastructure/DNS-OVER-TLS-SETUP.md new file mode 100644 index 0000000..b48df84 --- /dev/null +++ b/infrastructure/DNS-OVER-TLS-SETUP.md @@ -0,0 +1,269 @@ +# DNS over TLS Configuration Guide + +This guide covers setting up DNS over TLS (DoT) on your UCG Ultra gateway to encrypt DNS queries network-wide. + +## What is DNS over TLS? + +DNS over TLS encrypts DNS queries between your network and upstream DNS servers, preventing ISPs and other third parties from monitoring or logging your DNS lookups. Once configured on the UCG Ultra, all devices on your network automatically benefit without per-device configuration. + +## Prerequisites + +- UCG Ultra at 10.0.10.1 +- Admin access to UniFi Network application +- Internet connectivity + +## Configuration Steps + +### 1. Access UCG Ultra Web Interface + +``` +URL: https://10.0.10.1 +or access via UniFi Network application +``` + +### 2. Navigate to DNS Settings + +1. Click **Settings** (gear icon) +2. Select **Internet** +3. Click on **WAN** (or **Primary WAN1**) +4. Scroll to **DNS Servers** section + +### 3. Enable DNS over TLS + +1. Toggle **DNS over TLS** to **ON** +2. Configure upstream DNS servers (see options below) +3. Click **Apply Changes** + +## Recommended DNS Providers + +### Option 1: Cloudflare (Recommended - Privacy-focused, fastest) + +**DNS Servers:** +- Primary: `1.1.1.1` +- Secondary: `1.0.0.1` + +**TLS Hostname:** `cloudflare-dns.com` + +**Features:** +- Fastest global DNS resolver +- Privacy-focused (doesn't log queries) +- DNSSEC validation +- Malware blocking available (1.1.1.2/1.0.0.2) +- Family filtering available (1.1.1.3/1.0.0.3) + +### Option 2: Quad9 (Security-focused) + +**DNS Servers:** +- Primary: `9.9.9.9` +- Secondary: `149.112.112.112` + +**TLS Hostname:** `dns.quad9.net` + +**Features:** +- Blocks known malicious domains +- Privacy-focused (based in Switzerland) +- DNSSEC validation +- Threat intelligence from multiple sources + +### Option 3: Google Public DNS + +**DNS Servers:** +- Primary: `8.8.8.8` +- Secondary: `8.8.4.4` + +**TLS Hostname:** `dns.google` + +**Features:** +- High reliability and uptime +- Fast global network +- DNSSEC validation +- Note: Google logs queries for 24-48 hours + +### Option 4: AdGuard DNS (Ad Blocking) + +**DNS Servers (Default - Ad Blocking):** +- Primary: `94.140.14.14` +- Secondary: `94.140.15.15` + +**TLS Hostname:** `dns.adguard-dns.com` + +**Features:** +- Built-in ad and tracker blocking +- Family protection mode available +- No logging +- Free tier available + +## Additional Recommended Settings + +### Enable DNSSEC + +While in DNS settings: +1. Enable **DNSSEC** (if available) +2. This cryptographically validates DNS responses to prevent spoofing + +### Configure Local DNS Records + +For easier access to infrastructure services: + +**Settings โ†’ Networks โ†’ LAN โ†’ DHCP โ†’ Local DNS Records** + +Add custom entries: +- `proxmox.home` โ†’ `10.0.10.3` (main-pve) +- `proxmox-router.home` โ†’ `10.0.10.2` (pve-router) +- `storage.home` โ†’ `10.0.10.5` (OpenMediaVault) +- `homeassistant.home` โ†’ `10.0.10.24` (Home Assistant) +- `esphome.home` โ†’ `10.0.10.28` (ESPHome) +- `auth.home` โ†’ `10.0.10.21` (Authentik - when deployed) + +This allows you to access services via friendly names instead of IP addresses. + +## Verification + +### Test DNS over TLS is Working + +**Method 1: Cloudflare Test (if using Cloudflare DNS)** +1. Visit: https://1.1.1.1/help +2. Look for "Using DNS over TLS (DoT)" - should show **Yes** + +**Method 2: Command Line Test** + +From any device on your network: +```bash +# Test DNS resolution +nslookup google.com + +# Test DNSSEC validation (should succeed) +dig @1.1.1.1 dnssec-failed.org + +# Check if queries are encrypted (requires tcpdump) +sudo tcpdump -i any port 853 +# Should show TLS traffic on port 853, not plaintext DNS on port 53 +``` + +**Method 3: Check UCG Ultra Logs** + +In UniFi Network application: +1. Navigate to **System Settings โ†’ Logs** +2. Filter for DNS-related events +3. Should see successful DoT connections + +## Advanced: Local DNS Resolver Option + +For even more control (ad blocking, custom filtering, detailed logging), consider deploying a local DNS resolver: + +### Pi-hole or AdGuard Home + +**Deployment:** +- LXC container on pve-router (10.0.10.2) +- IP: 10.0.10.26 (available in allocation plan) +- Resources: 1 CPU, 512MB-1GB RAM, 4GB storage + +**Configuration:** +1. Deploy Pi-hole/AdGuard Home on 10.0.10.26 +2. Configure it to use DoT upstream (Cloudflare, Quad9, etc.) +3. Point UCG Ultra DNS to 10.0.10.26 +4. All network traffic โ†’ Pi-hole โ†’ DoT upstream + +**Benefits:** +- Network-wide ad blocking +- Detailed query logging and statistics +- Custom blocklists and whitelists +- Local DNS record management +- DoT encryption for upstream queries + +See `PIHOLE-SETUP.md` (future document) for deployment guide. + +## Troubleshooting + +### DNS Resolution Fails After Enabling DoT + +**Symptoms:** Websites won't load, "DNS resolution failed" errors + +**Solutions:** +1. Verify upstream DNS servers are correct +2. Check UCG Ultra has internet connectivity +3. Temporarily disable DoT to isolate issue +4. Try different DNS provider (Cloudflare vs Quad9) +5. Check firewall rules allow outbound port 853 (DoT) + +### Slow DNS Resolution + +**Possible Causes:** +- Upstream DNS server is slow/distant +- Network latency issues +- DNSSEC validation overhead + +**Solutions:** +1. Try different DNS provider closer to your region +2. Use DNS benchmark tool: https://www.grc.com/dns/benchmark.htm +3. Check UCG Ultra CPU/memory usage + +### Some Devices Can't Resolve DNS + +**Check:** +1. Device is using UCG Ultra as DNS (10.0.10.1) +2. Device has valid DHCP lease +3. No hardcoded DNS servers on the device +4. Firewall rules aren't blocking DNS + +## Security Considerations + +### What DoT Protects Against +- ISP DNS query logging and selling data +- DNS query snooping on local network +- Man-in-the-middle DNS hijacking + +### What DoT Does NOT Protect Against +- Website tracking (cookies, fingerprinting) +- ISP seeing which websites you visit (they see IP addresses) +- Malware/phishing (use Quad9 or filtering DNS for this) + +### Additional Privacy Measures +- Use HTTPS everywhere (encrypted web traffic) +- Consider VPN for full traffic encryption +- Use privacy-focused browsers (Firefox, Brave) +- Enable tracking protection in browsers + +## Maintenance + +### Regular Checks +- **Monthly:** Verify DoT is still active (check test sites) +- **Quarterly:** Review DNS provider performance +- **As Needed:** Update local DNS records for new services + +### When to Reconfigure +- Moving to new internet provider +- DNS provider changes policies +- Performance degrades +- Adding local DNS resolver (Pi-hole) + +## Network-Wide Impact + +Once configured, DNS over TLS benefits: +- All computers (Windows, Mac, Linux) +- Mobile devices (phones, tablets) +- IoT devices (smart home, cameras, etc.) +- Guest network devices +- All VLANs managed by UCG Ultra + +**No per-device configuration needed.** + +## Related Documentation + +- `IP-ALLOCATION.md` - Network addressing plan +- `RUNBOOK.md` - General network troubleshooting +- `SERVICES.md` - Service configuration reference +- Future: `PIHOLE-SETUP.md` - Local DNS resolver deployment + +## References + +- [Cloudflare DNS over TLS](https://developers.cloudflare.com/1.1.1.1/encryption/dns-over-tls/) +- [Quad9 Documentation](https://www.quad9.net/support/faq/) +- [DNS over TLS RFC 7858](https://datatracker.ietf.org/doc/html/rfc7858) +- [UniFi Gateway Documentation](https://help.ui.com/hc/en-us/categories/200320654-UniFi-Gateway) + +--- + +**Last Updated:** 2025-11-18 +**Status:** Ready for deployment +**Priority:** Medium (privacy/security enhancement) diff --git a/infrastructure/IMPROVEMENTS.md b/infrastructure/IMPROVEMENTS.md new file mode 100644 index 0000000..8e1f8a8 --- /dev/null +++ b/infrastructure/IMPROVEMENTS.md @@ -0,0 +1,451 @@ +# Infrastructure Improvement Recommendations + +Based on the infrastructure audit checklist, this document outlines recommended improvements for security, reliability, and operational efficiency. + +## Table of Contents +- [High Priority Improvements](#high-priority-improvements) +- [Security Enhancements](#security-enhancements) +- [Reliability & Availability](#reliability--availability) +- [Monitoring & Observability](#monitoring--observability) +- [Automation Opportunities](#automation-opportunities) +- [Documentation & Knowledge Management](#documentation--knowledge-management) +- [Capacity Planning](#capacity-planning) +- [Cost Optimization](#cost-optimization) + +--- + +## High Priority Improvements + +### 1. Implement Automated Backups + +**Current State**: Manual or ad-hoc backups +**Target State**: Automated, scheduled backups with verification + +**Action Items**: +- [ ] Set up automated Proxmox VM/Container backups (see `scripts/backup-proxmox.sh`) +- [ ] Configure automatic backup of VPS configurations +- [ ] Implement off-site backup sync (to cloud storage or remote location) +- [ ] Schedule regular backup restoration tests +- [ ] Set up backup monitoring and alerting + +**Priority**: ๐Ÿ”ด Critical +**Estimated Effort**: 4-8 hours +**Benefits**: Data loss prevention, faster disaster recovery + +--- + +### 2. SSL Certificate Auto-Renewal + +**Current State**: Manual certificate management +**Target State**: Automated certificate renewal with monitoring + +**Action Items**: +- [ ] Install and configure certbot with auto-renewal +- [ ] Set up certbot systemd timer: `systemctl enable certbot.timer` +- [ ] Configure renewal hooks to reload services +- [ ] Monitor certificate expiration dates +- [ ] Consider wildcard certificates to simplify management + +**Priority**: ๐Ÿ”ด Critical +**Estimated Effort**: 2-4 hours +**Benefits**: Prevent service outages from expired certificates + +**Implementation**: +```bash +# Enable auto-renewal +sudo systemctl enable certbot.timer +sudo systemctl start certbot.timer + +# Test renewal +sudo certbot renew --dry-run + +# Add renewal hook for Pangolin +echo "systemctl reload pangolin" | sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-pangolin.sh +sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-pangolin.sh +``` + +--- + +### 3. Implement Basic Monitoring + +**Current State**: No centralized monitoring +**Target State**: Uptime monitoring with alerts for critical services + +**Action Items**: +- [ ] Deploy Uptime Kuma for service monitoring (lightweight, easy to set up) +- [ ] Configure health checks for all public services +- [ ] Set up alerting (email, SMS, or Slack) +- [ ] Monitor VPS resources (CPU, RAM, disk) +- [ ] Monitor Proxmox node resources +- [ ] Track Gerbil tunnel status + +**Priority**: ๐ŸŸ  High +**Estimated Effort**: 4-6 hours +**Benefits**: Early detection of issues, reduced downtime + +See [MONITORING.md](MONITORING.md) for detailed setup instructions. + +--- + +## Security Enhancements + +### 4. Harden SSH Access + +**Recommendations**: +- [ ] Disable password authentication (key-only) +- [ ] Change default SSH port on VPS +- [ ] Implement fail2ban for brute force protection +- [ ] Use SSH certificate authority for easier key management +- [ ] Enable 2FA for SSH (Google Authenticator) + +**Implementation**: +```bash +# /etc/ssh/sshd_config +PasswordAuthentication no +PubkeyAuthentication yes +PermitRootLogin prohibit-password +Port 2222 # Non-standard port + +# Install fail2ban +sudo apt install fail2ban +sudo systemctl enable fail2ban +``` + +**Priority**: ๐ŸŸ  High +**Estimated Effort**: 2-3 hours + +--- + +### 5. Implement Network Segmentation + +**Current State**: Flat network +**Target State**: VLANs separating different service tiers + +**Recommendations**: +- [ ] VLAN 10: Management (Proxmox, OMV admin interfaces) +- [ ] VLAN 20: Production Services +- [ ] VLAN 30: Development/Testing +- [ ] VLAN 40: IoT/Untrusted devices +- [ ] Configure firewall rules between VLANs + +**Priority**: ๐ŸŸก Medium +**Estimated Effort**: 8-12 hours +**Benefits**: Improved security, network isolation, easier troubleshooting + +--- + +### 6. Secrets Management + +**Current State**: Credentials in config files or documentation +**Target State**: Centralized secrets management + +**Recommendations**: +- [ ] Use environment variables for sensitive data +- [ ] Implement Bitwarden/Vaultwarden for password management +- [ ] Consider HashiCorp Vault for API keys and certificates +- [ ] Encrypt sensitive files with GPG or age +- [ ] Never commit secrets to git + +**Priority**: ๐ŸŸ  High +**Estimated Effort**: 4-6 hours + +--- + +### 7. Regular Security Updates + +**Recommendations**: +- [ ] Enable unattended-upgrades for security patches +- [ ] Schedule monthly maintenance windows for updates +- [ ] Subscribe to security mailing lists for critical software +- [ ] Implement vulnerability scanning + +**Implementation**: +```bash +# Enable automatic security updates +sudo apt install unattended-upgrades +sudo dpkg-reconfigure --priority=low unattended-upgrades +``` + +**Priority**: ๐ŸŸ  High +**Estimated Effort**: 2-3 hours + +--- + +## Reliability & Availability + +### 8. Implement High Availability for Critical Services + +**Recommendations**: +- [ ] Run critical services on both Proxmox nodes +- [ ] Set up floating IP or load balancing +- [ ] Configure automatic failover +- [ ] Use Proxmox HA features for critical VMs + +**Priority**: ๐ŸŸก Medium +**Estimated Effort**: 8-16 hours + +--- + +### 9. Backup VPS Provider Relationship + +**Recommendations**: +- [ ] Document procedures for spinning up with alternate VPS provider +- [ ] Keep configuration backups accessible outside primary VPS +- [ ] Test VPS migration annually +- [ ] Consider multi-region deployment for critical services + +**Priority**: ๐ŸŸก Medium +**Estimated Effort**: 4-6 hours + +--- + +### 10. UPS and Power Management + +**Recommendations**: +- [ ] Install UPS on all Proxmox nodes +- [ ] Configure Network UPS Tools (NUT) for graceful shutdown +- [ ] Test power failure procedures +- [ ] Document power-on sequence after outage + +**Priority**: ๐ŸŸ  High (if not already implemented) +**Estimated Effort**: 3-4 hours (plus hardware cost) + +--- + +## Monitoring & Observability + +### 11. Comprehensive Monitoring Stack + +**Recommendations**: +- [ ] Deploy Prometheus for metrics collection +- [ ] Set up Grafana for visualization +- [ ] Configure Loki for log aggregation +- [ ] Implement Alertmanager for alerting +- [ ] Create dashboards for key metrics + +**Dashboards to Create**: +- VPS resource utilization +- Proxmox cluster overview +- Storage capacity trends +- Service uptime and response times +- Gerbil tunnel status + +**Priority**: ๐ŸŸก Medium +**Estimated Effort**: 12-16 hours +**See**: [MONITORING.md](MONITORING.md) + +--- + +### 12. Centralized Logging + +**Recommendations**: +- [ ] Aggregate logs from all services to central location +- [ ] Implement log retention policies +- [ ] Set up log-based alerts for errors +- [ ] Create log analysis dashboards + +**Priority**: ๐ŸŸก Medium +**Estimated Effort**: 6-8 hours + +--- + +## Automation Opportunities + +### 13. Infrastructure as Code + +**Current State**: Manual configuration +**Target State**: Automated, version-controlled infrastructure + +**Recommendations**: +- [ ] Document VPS setup as Ansible playbooks +- [ ] Use Terraform for DNS and cloud resources +- [ ] Create Proxmox VM templates with cloud-init +- [ ] Version control all automation + +**Priority**: ๐ŸŸก Medium +**Estimated Effort**: 16-24 hours +**Benefits**: Reproducible infrastructure, faster recovery, documentation + +--- + +### 14. Automated Health Checks + +**Recommendations**: +- [ ] Create scheduled health check scripts (see `scripts/health-check.sh`) +- [ ] Automated service restart on failure +- [ ] Self-healing for common issues +- [ ] Integration with monitoring system + +**Priority**: ๐ŸŸก Medium +**Estimated Effort**: 4-6 hours + +--- + +### 15. Certificate Management Automation + +**Recommendations**: +- [ ] Automate certificate deployment to all services +- [ ] Automated service reloads after certificate renewal +- [ ] Certificate expiration monitoring +- [ ] Automated DNS validation for wildcard certs + +**Priority**: ๐ŸŸ  High +**Estimated Effort**: 3-4 hours + +--- + +## Documentation & Knowledge Management + +### 16. Living Documentation + +**Current State**: Basic documentation +**Target State**: Comprehensive, up-to-date documentation + +**Action Items**: +- [x] Complete infrastructure audit checklist +- [x] Create RUNBOOK.md with operational procedures +- [x] Create DISASTER-RECOVERY.md +- [x] Create SERVICES.md +- [ ] Fill in all service details in SERVICES.md +- [ ] Document network topology diagram +- [ ] Create quick reference cards for common tasks +- [ ] Schedule quarterly documentation reviews + +**Priority**: ๐ŸŸ  High +**Estimated Effort**: Ongoing + +--- + +### 17. Runbook Automation + +**Recommendations**: +- [ ] Convert manual procedures to scripts where possible +- [ ] Create interactive troubleshooting guides +- [ ] Document lessons learned from incidents +- [ ] Share knowledge across team + +**Priority**: ๐ŸŸก Medium +**Estimated Effort**: Ongoing + +--- + +## Capacity Planning + +### 18. Resource Monitoring and Trending + +**Recommendations**: +- [ ] Track resource utilization over time +- [ ] Set up alerts for capacity thresholds (80%, 90%) +- [ ] Create capacity planning reports +- [ ] Plan for growth based on trends + +**Metrics to Track**: +- CPU utilization per node +- RAM usage per node +- Storage growth rate (OMV) +- Network bandwidth utilization +- Number of VMs/containers + +**Priority**: ๐ŸŸก Medium +**Estimated Effort**: 4-6 hours (plus ongoing) + +--- + +### 19. Resource Right-Sizing + +**Recommendations**: +- [ ] Review VM/container resource allocations +- [ ] Identify over-provisioned VMs +- [ ] Identify resource-constrained VMs +- [ ] Adjust allocations based on actual usage + +**Priority**: ๐ŸŸข Low +**Estimated Effort**: 2-4 hours + +--- + +## Cost Optimization + +### 20. VPS Cost Review + +**Recommendations**: +- [ ] Compare current VPS pricing with alternatives +- [ ] Consider reserved instances or annual billing +- [ ] Evaluate if all VPS resources are utilized +- [ ] Review bandwidth usage and overage costs + +**Priority**: ๐ŸŸข Low +**Estimated Effort**: 2-3 hours + +--- + +### 21. Power Consumption Optimization + +**Recommendations**: +- [ ] Enable CPU power management features +- [ ] Schedule non-critical services for off-peak hours +- [ ] Consider shutting down development VMs overnight +- [ ] Monitor power consumption + +**Priority**: ๐ŸŸข Low +**Estimated Effort**: 3-4 hours + +--- + +## Implementation Roadmap + +### Phase 1: Critical (Weeks 1-2) +1. Automated backups with off-site storage +2. SSL certificate auto-renewal +3. SSH hardening and fail2ban +4. Basic uptime monitoring + +### Phase 2: High Priority (Weeks 3-6) +1. Comprehensive monitoring stack +2. Security updates automation +3. Secrets management +4. Documentation completion +5. Health check automation + +### Phase 3: Medium Priority (Weeks 7-12) +1. Network segmentation with VLANs +2. High availability for critical services +3. Infrastructure as Code implementation +4. Centralized logging +5. Capacity planning processes + +### Phase 4: Ongoing +1. Regular security audits +2. Documentation maintenance +3. Performance optimization +4. Cost reviews +5. DR testing + +--- + +## Success Metrics + +Track the following to measure improvement: + +| Metric | Current | Target | +|--------|---------|--------| +| Mean Time To Recovery (MTTR) | _____ | < 1 hour | +| Backup success rate | _____ | 100% | +| Service uptime | _____ | 99.9% | +| Certificate renewal failures | _____ | 0 | +| Security patches applied within | _____ | 7 days | +| Unplanned outages per month | _____ | < 1 | +| Time to detect issues | _____ | < 5 minutes | + +--- + +## Notes + +- Prioritize improvements based on your specific needs and risk tolerance +- Review and update this document quarterly +- Track implementation progress +- Measure impact of improvements + +**Last Updated**: _____________ +**Next Review**: _____________ +**Version**: 1.0 diff --git a/infrastructure/INFRASTRUCTURE-TODO.md b/infrastructure/INFRASTRUCTURE-TODO.md new file mode 100644 index 0000000..248b4e0 --- /dev/null +++ b/infrastructure/INFRASTRUCTURE-TODO.md @@ -0,0 +1,360 @@ +# Infrastructure TODO List + +**Created:** 2025-12-29 +**Last Updated:** 2025-12-29 +**Status:** Active development tasks + +This document tracks all incomplete infrastructure tasks and future improvements. + +--- + +## โœ… Completed Items + +### 1. Fix Home Assistant Public Domain Access + +**Status**: โœ… COMPLETED (2025-12-29) + +**What was done**: +1. Updated Caddy to use HTTPS backend for Home Assistant +2. Added VPS WireGuard IP (10.0.8.1) to Home Assistant's trusted_proxies +3. Verified bob.nianticbooks.com is accessible + +**Result**: All 5 public domains now working: +- โœ… freddesk.nianticbooks.com โ†’ Proxmox +- โœ… bob.nianticbooks.com โ†’ Home Assistant +- โœ… ad5m.nianticbooks.com โ†’ 3D Printer +- โœ… auth.nianticbooks.com โ†’ Authentik SSO +- โœ… bible.nianticbooks.com โ†’ Bible reading plan + +### 2. Deploy RustDesk ID Server + +**Status**: โœ… COMPLETED (2025-12-25) + +**What was deployed**: +1. ID Server (hbbs) on main-pve LXC 123 at 10.0.10.23 +2. Relay Server (hbbr) on VPS at 66.63.182.168:21117 +3. Generated encryption key pair +4. Verified client connectivity + +**Result**: RustDesk fully operational +- โœ… ID Server (hbbs): 10.0.10.23 ports 21115, 21116, 21118 +- โœ… Relay Server (hbbr): VPS port 21117 +- โœ… Public Key: `sfYuCTMHxrA22kukomb/RAKYyUgr8iaMfm/U4CFLfL0=` +- โœ… Client Configuration: ID Server `66.63.182.168`, Key included +- โœ… Version: 1.1.14 (both servers) + +**Documentation**: +- SERVICES.md - Service inventory and health checks +- guides/RUSTDESK-DEPLOYMENT-COMPLETE.md - Complete deployment guide + +--- + +## Medium Priority + +### 3. Deploy Prometheus + Grafana Monitoring + +**Status**: โœ… DISCOVERED - Already deployed (2025-12-29) + +**Current State**: +- **Location**: 10.0.10.25 (responding to ping) +- **Grafana**: Port 3000 โœ… Running (redirects to /login) +- **Prometheus**: Port 9090 โœ… Running +- **Deployment Method**: TBD (need to investigate) + +**Remaining Configuration Tasks**: +1. Document deployment method (Docker Compose, systemd, VM/Container type) +2. Configure PostgreSQL database on 10.0.10.20 for Grafana (if not already done) +3. Set up Authentik SSO for Grafana +4. Configure Prometheus monitoring targets: + - Proxmox nodes (via node_exporter) + - VPS (WireGuard tunnel metrics) + - PostgreSQL + - Home Assistant + - Other services +5. Import Grafana dashboards: + - Proxmox overview + - PostgreSQL metrics + - Network metrics +6. Set up alerting (email/Slack) +7. Optionally add Caddy public route + +**Priority**: Low-Medium (services running, configuration needed) + +**Note**: This was discovered during the infrastructure audit. The basic services are operational, but monitoring targets and dashboards need configuration. + +--- + +## Low Priority (Cleanup) + +### 4. Remove Deprecated VMs + +**Objective**: Reclaim resources from unused services + +**Status**: โธ๏ธ Deferred - Non-critical + +#### 4.1 Remove Spoolman VM + +**Current State**: +- IP: 10.0.10.71 (allocated but not in use) +- Reason: Bambu printer incompatible, service no longer needed + +**Steps**: +1. Verify no dependencies: `pct/qm status ` +2. Backup if needed: `vzdump --storage backup` +3. Stop VM/container: `pct stop ` or `qm stop ` +4. Delete: `pct destroy ` or `qm destroy ` +5. Remove Pangolin route (if exists) +6. Update IP-ALLOCATION.md to mark 10.0.10.71 as available +7. Update documentation + +**Priority**: Low + +**Estimated Time**: 15 minutes + +#### 4.2 Remove Authelia VM + +**Current State**: +- IP: 10.0.10.112 (allocated but not in use) +- Reason: Replaced by Authentik SSO + +**Steps**: +1. Verify Authentik is working for all services +2. Backup Authelia config for reference (if needed) +3. Stop VM/container: `pct stop ` or `qm stop ` +4. Delete: `pct destroy ` or `qm destroy ` +5. Update IP-ALLOCATION.md to mark 10.0.10.112 as available (or remove from list) +6. Update documentation + +**Priority**: Low + +**Estimated Time**: 15 minutes + +--- + +## Future Enhancements + +### 5. n8n + Claude Code Advanced Features + +**Objective**: Enhance n8n and Claude Code integration + +**Status**: โœ… Basic integration working, advanced features optional + +**Remaining Optional Tasks** (from MIGRATION-CHECKLIST.md 6.4): +- [ ] Session management workflow (UUID generation, multi-turn conversations) +- [ ] Slack integration (Slack โ†’ n8n โ†’ Claude Code โ†’ Slack) +- [ ] Tool deployment with `--dangerously-skip-permissions` flag +- [ ] Error handling (network disconnect, invalid commands) +- [ ] Resource monitoring during heavy Claude operations +- [ ] Production hardening: + - SSH timeout configuration + - Output length limits + - Logging for Claude executions + - Error notifications + - Optional Caddy route for public n8n access (with Authentik SSO) + +**Reference**: +- MIGRATION-CHECKLIST.md section 6.4 +- N8N-CLAUDE-STATUS.md + +**Priority**: Low (nice-to-have, basic functionality working) + +**Estimated Time**: 2-4 hours for each feature + +--- + +### 6. Home Assistant Enhancements + +#### 6.1 Configure Local HTTPS Certificates + +**Objective**: Use local CA certificates for internal HTTPS access + +**Status**: โธ๏ธ Deferred (CA setup complete, deployment pending) + +**Details**: +- CA already set up (HTTPS-SETUP-STATUS.md from 2025-12-06) +- Certificates generated for services +- Need to deploy certificates to Home Assistant and other services + +**Steps** (from HTTPS-SETUP-STATUS.md): +1. Copy certificates to Home Assistant: + ```bash + scp ~/certs/bob.crt ~/certs/bob.key root@10.0.10.24:/config/ssl/ + ``` +2. Update Home Assistant configuration: + ```yaml + http: + ssl_certificate: /config/ssl/bob.crt + ssl_key: /config/ssl/bob.key + server_port: 8123 + ``` +3. Restart Home Assistant +4. Trust CA on client devices + +**Note**: Current setup uses local CA certificate. Public domain uses Caddy with Let's Encrypt. + +**Priority**: Low (HTTPS already working with local CA cert) + +**Estimated Time**: 30 minutes + +#### 6.2 Integrate More Services with Authentik SSO + +**Objective**: Single sign-on for additional services + +**Status**: ๐Ÿ“‹ Planned + +**Completed**: +- โœ… Proxmox (all 3 hosts) +- โœ… Grafana (OAuth2 configured) + +**Not Possible**: +- โŒ n8n (requires Enterprise license for OIDC/SSO) + +**Pending**: +- [ ] Home Assistant (complex - requires proxy provider or LDAP) +- [ ] Other services as they're deployed + +**Priority**: Low (manual login acceptable for now) + +**Estimated Time**: 1-2 hours per service + +--- + +### 7. Backup Strategy Completion + +**Objective**: Implement full 3-tier backup system + +**Status**: โœ… Tier 1 complete, Tier 2-3 planned + +**Current State** (from CLAUDE.md): +- โœ… Tier 1 (Local/OMV NFS): Fully operational + - PostgreSQL backups: Daily 2:00 AM + - Proxmox VM/container backups: Daily 2:30 AM + - Retention: 7 days daily, 4 weeks weekly, 3 months monthly + +**Remaining Tiers**: +- [ ] Tier 2: Off-site external drives (manual rotation) +- [ ] Tier 3: Backblaze B2 cloud storage (automated) + +**Reference**: +- guides/HOMELAB-BACKUP-STRATEGY.md +- guides/BACKUP-QUICK-START.md + +**Priority**: Medium (Tier 1 provides good protection, Tier 2-3 for disaster recovery) + +**Estimated Time**: 2-4 hours for Tier 3 cloud setup + +--- + +### 8. Monitoring & Alerting + +**Objective**: Proactive monitoring of infrastructure health + +**Status**: ๐Ÿ“‹ Planned (prerequisite: Prometheus + Grafana deployment) + +**Components**: +- [ ] Service uptime monitoring +- [ ] Resource utilization (CPU, RAM, disk) +- [ ] Network connectivity (WireGuard tunnel status) +- [ ] Backup success/failure alerts +- [ ] Certificate expiration warnings +- [ ] Disk space alerts (OMV storage) + +**Alerting Methods**: +- Email +- Slack/Discord webhook +- Home Assistant notifications + +**Priority**: Medium (blocked by Prometheus deployment) + +**Estimated Time**: 2-3 hours (after Prometheus is deployed) + +--- + +### 9. Cleanup and Archive Old Documentation + +**Objective**: Remove or archive outdated status documents + +**Status**: ๐Ÿ“‹ Pending + +**Files to Archive or Update**: +1. **wireguard-setup-progress.md** + - Status: Outdated (from November 2025) + - Contains old troubleshooting info that's no longer relevant + - WireGuard now operational (verified 2025-12-29) + - Action: Archive to `docs/archive/` or delete + +2. **HTTPS-SETUP-STATUS.md** + - Status: Partially outdated (from December 6, 2025) + - CA setup complete, but local cert deployment not done + - Services using Caddy with Let's Encrypt for public access + - Action: Archive or update with current HTTPS status + +3. **N8N-CLAUDE-STATUS.md** + - Status: Partially outdated + - Basic integration complete + - Many "TODO" items that are now optional + - Action: Archive or consolidate into SERVICES.md + +**Priority**: Low + +**Estimated Time**: 30 minutes + +--- + +## Documentation Maintenance + +### 10. Keep Documentation Updated + +**Objective**: Maintain accurate infrastructure documentation + +**Regular Tasks**: +- [ ] Update SERVICES.md when services change +- [ ] Update IP-ALLOCATION.md for new devices +- [ ] Update MIGRATION-CHECKLIST.md for completed phases +- [ ] Update INFRASTRUCTURE-TODO.md (this file) as tasks are completed +- [ ] Update CLAUDE.md when architecture changes + +**Frequency**: As changes occur + +**Priority**: Ongoing + +--- + +## Quick Reference: IP Addresses Still Available + +### Reserved but Unused (Available for new services): +- 10.0.10.6-9 (infrastructure expansion) +- 10.0.10.11-12, 10.0.10.14-19 (management) +- 10.0.10.23 (RustDesk - planned) +- 10.0.10.25 (Prometheus/Grafana - planned) +- 10.0.10.26 (production services) +- 10.0.10.28 (was ESPHome - now runs as HA add-on, IP available) +- 10.0.10.31-39 (IoT devices) +- 10.0.10.41-49 (utility services) + +### To Be Reclaimed (after cleanup): +- 10.0.10.71 (Spoolman - to be removed) +- 10.0.10.112 (Authelia - to be removed) + +--- + +## Notes + +- All critical infrastructure is operational (verified 2025-12-29) +- WireGuard tunnel stable and functional +- Public domains working (except Home Assistant HTTPS backend) +- PostgreSQL shared database serving multiple services +- Authentik SSO integrated with Proxmox cluster +- Automated backups operational (Tier 1 local/NFS) + +**Next High-Value Tasks**: +1. โœ… ~~Fix Home Assistant public domain~~ - COMPLETED +2. โœ… ~~Discover/Document Prometheus + Grafana~~ - COMPLETED +3. โœ… ~~Discover/Document RustDesk~~ - COMPLETED +4. Configure Prometheus monitoring targets and Grafana dashboards +5. Cleanup deprecated VMs (Spoolman, Authelia) + +--- + +**Last Updated**: 2025-12-29 +**Updated By**: Fred (with Claude Code) diff --git a/infrastructure/IP-ALLOCATION.md b/infrastructure/IP-ALLOCATION.md new file mode 100644 index 0000000..2aa1415 --- /dev/null +++ b/infrastructure/IP-ALLOCATION.md @@ -0,0 +1,262 @@ +# Network IP Allocation Plan + +**Last Updated:** 2026-01-18 +**Status:** Active - Source of Truth +**Network:** 10.0.10.0/24 +**Gateway:** 10.0.10.1 (UCG Ultra) + +--- + +## IP Range Allocation + +| Range | Purpose | Count | Method | +|-------|---------|-------|--------| +| 10.0.10.1-9 | **Core Infrastructure** | 9 | Static on device | +| 10.0.10.10-19 | **Management & Remote Access** | 10 | Static on device | +| 10.0.10.20-29 | **Production Services** | 10 | Static on device | +| 10.0.10.30-39 | **IoT & 3D Printing** | 10 | Static/Reserved | +| 10.0.10.40-49 | **Utility Services & Gaming** | 10 | Static on device | +| 10.0.10.50-254 | **DHCP Pool** | 205 | Dynamic | + +**Note:** IPs 10.0.10.1-49 use static configuration on devices, NOT DHCP reservations on UCG Ultra. + +--- + +## Detailed IP Assignments + +### Core Infrastructure (10.0.10.1-9) + +| IP | Hostname | Device/Service | Location | CT/VM ID | Status | +|----|----------|----------------|----------|----------|--------| +| 10.0.10.1 | ucg-ultra | UCG Ultra Gateway | - | - | Active | +| 10.0.10.2 | pve-router | i5 Proxmox Node (8c/8GB) | Office | Host | Active | +| 10.0.10.3 | main-pve | DL380p Proxmox (32c/96GB) | Remote | Host | Active | +| 10.0.10.4 | pve-storage | Proxmox Host for OMV | - | Host | Active | +| 10.0.10.5 | omv | OpenMediaVault (12TB) | pve-storage | VM 400 | Active | +| 10.0.10.6 | - | AVAILABLE | - | - | - | +| 10.0.10.7 | - | AVAILABLE | - | - | - | +| 10.0.10.8 | - | AVAILABLE | - | - | - | +| 10.0.10.9 | - | AVAILABLE | - | - | - | + +### Management & Remote Access (10.0.10.10-19) + +| IP | Hostname | Device/Service | Location | CT/VM ID | Status | +|----|----------|----------------|----------|----------|--------| +| 10.0.10.10 | homelab-command | Gaming PC (RTX 5060, Wyoming, Ollama) | Office | Physical | Active | +| 10.0.10.11 | freds-imac | Fred's iMac (Late 2013, 3.2GHz i5, 24GB RAM, OpenClaw Desktop, user: fredi5) - Ethernet | Office | Physical | Configured | +| 10.0.10.12 | - | AVAILABLE | - | - | - | +| 10.0.10.13 | ilo | HP iLO (DL380p Management) | Remote | Physical | Active | +| 10.0.10.14 | - | AVAILABLE | - | - | - | +| 10.0.10.15 | ca-server | Step-CA Certificate Authority | main-pve | CT 115 | Active | +| 10.0.10.16 | - | AVAILABLE | - | - | - | +| 10.0.10.17 | - | AVAILABLE | - | - | - | +| 10.0.10.18 | - | AVAILABLE | - | - | - | +| 10.0.10.19 | - | AVAILABLE | - | - | - | + +**Note on Fred's iMac:** +- **Ethernet (en0)**: 10.0.10.11 (Static) - MAC: ac:87:a3:2b:43:62 - **Status: Configured, cable not connected** +- **Wi-Fi (en1)**: 10.0.10.144 (DHCP) - MAC: b8:09:8a:ca:6c:53 - **Status: Active** +- When Ethernet cable is connected, both interfaces will be active simultaneously +- OpenClaw Desktop client accessible via either IP + +### Production Services (10.0.10.20-29) + +| IP | Hostname | Service | Location | CT/VM ID | Status | +|----|----------|---------|----------|----------|--------| +| 10.0.10.20 | postgresql | PostgreSQL (Shared DB) | main-pve | CT 102 | Active | +| 10.0.10.21 | authentik | Authentik SSO | main-pve | CT 121 | Active | +| 10.0.10.22 | n8n | n8n Workflow Automation | main-pve | CT 106 | Active | +| 10.0.10.23 | rustdesk | RustDesk ID Server (hbbs) | main-pve | CT 123 | Active | +| 10.0.10.24 | homeassistant | Home Assistant OS | pve-router | VM 104 | Active | +| 10.0.10.25 | prometheus | Prometheus + Grafana | main-pve | CT 125 | Active | +| 10.0.10.26 | uptime-kuma | Uptime Kuma Monitoring | main-pve | CT 128 | Active | +| 10.0.10.27 | dockge | Dockge + Media Stack (Sonarr, Radarr, Prowlarr, Bazarr, Deluge, Calibre-Web) + Vikunja (deprecated) + Dashboard + Caddy Internal Proxy | main-pve | CT 127 | Active | +| 10.0.10.28 | openclaw | OpenClaw Gateway (Multi-Agent AI Coordinator) - Port 18789 | main-pve | CT 130 | Active | +| 10.0.10.29 | - | AVAILABLE | - | - | - | + +### IoT & 3D Printing (10.0.10.30-39) + +| IP | Hostname | Device | MAC Address | Status | +|----|----------|--------|-------------|--------| +| 10.0.10.30 | ad5m | Flashforge AD5M 3D Printer | 88:a9:a7:99:c3:64 | Active | +| 10.0.10.31 | bambu-a1 | Bambu Lab A1 3D Printer | cc:ba:97:21:4c:f8 | Active | +| 10.0.10.32 | - | AVAILABLE | - | - | +| 10.0.10.33 | - | AVAILABLE | - | - | +| 10.0.10.34 | - | AVAILABLE | - | - | +| 10.0.10.35 | vehicle-tracker | Vehicle Maintenance Tracker (FastAPI) - CT 135 main-pve | - | Planned | +| 10.0.10.36 | - | AVAILABLE | - | - | +| 10.0.10.37 | - | AVAILABLE | - | - | +| 10.0.10.38 | - | AVAILABLE | - | - | +| 10.0.10.39 | - | AVAILABLE | - | - | + +### Utility Services & Gaming (10.0.10.40-49) + +| IP | Hostname | Service | Location | CT/VM ID | Status | +|----|----------|---------|----------|----------|--------| +| 10.0.10.40 | bar-assistant | Cocktail Recipe Manager | main-pve | CT 103 | Active | +| 10.0.10.41 | minecraft-forge | Minecraft Forge (CFMRPGU) | main-pve | CT 130 | Active | +| 10.0.10.42 | minecraft-stoneblock4 | Minecraft Stoneblock 4 | main-pve | CT 131 | Active | +| 10.0.10.43 | - | AVAILABLE | - | - | - | +| 10.0.10.44 | - | AVAILABLE | - | - | - | +| 10.0.10.45 | pterodactyl-panel | Pterodactyl Game Panel | main-pve | CT 105 | Active | +| 10.0.10.46 | pterodactyl-wings | Pterodactyl Wings (Node) | main-pve | CT 107 | Active | +| 10.0.10.47 | - | AVAILABLE | - | - | - | +| 10.0.10.48 | - | AVAILABLE | - | - | - | +| 10.0.10.49 | - | AVAILABLE | - | - | - | + +--- + +## DHCP Pool Devices (10.0.10.50-254) + +These devices receive dynamic IPs from UCG Ultra DHCP. Some have DHCP reservations. + +### Fixed DHCP Reservations (on UCG Ultra) + +| IP | Hostname | Device | MAC Address | DNS Record | +|----|----------|--------|-------------|------------| +| 10.0.10.179 | twingate-connector | Twingate Zero-Trust | bc:24:11:26:54:60 | - | +| 10.0.10.204 | cutter | Cutter iMac | 7c:c3:a1:af:d6:93 | cutter.nianticbooks.home | + +### Known Dynamic Devices (as of 2026-01-13) + +**Computers & Workstations:** +| IP | Hostname | Device | MAC Address | +|----|----------|--------|-------------| +| .105 | Freds-Mac-Pro | Jill's MacPro | 80:00:6e:f2:13:52 | +| .116 | HP8610 | HP Printer | 6c:c2:17:53:4e:f8 | +| .144 | Freds-iMac-WiFi | Fred's iMac Wi-Fi (Late 2013, 3.2GHz i5, 24GB RAM, OpenClaw Desktop, macOS Sequoia, user: fredi5) | b8:09:8a:ca:6c:53 | +| .156 | KobePC | Kobe's PC | 64:5d:86:15:de:20 | +| .157 | TP15 | ThinkPad 15 | 78:20:51:f6:9d:d0 | +| .162 | TP25 | ThinkPad 25 | b0:19:21:df:79:30 | +| .213 | Kevin-PC | Kevin's PC | a0:ad:9f:30:8c:af | + +**Smart Home & IoT:** +| IP | Device | MAC Address | +|----|--------|-------------| +| .62 | SolarEdge SE7K Inverter | 84:d6:c5:4a:70:32 | +| .170 | TY_WR (Tuya Device) | 68:57:2d:b4:dd:25 | +| .185 | GoveeLife Tower Fan | 98:17:3c:90:5e:aa | +| .190 | Ecobee Thermostat | 44:61:32:90:e0:a3 | +| .154 | Blink XT Camera | ac:41:6a:69:3a:8e | +| .176 | Blink Sync Module 2 | e8:4c:4a:12:03:32 | +| .189 | Sony PlayStation 5 | 70:66:2a:b2:3f:ec | +| .235 | Jill's Monitor | a8:2c:3e:bc:e2:bf | + +**Mesh WiFi (eero):** +| IP | Device | MAC Address | +|----|--------|-------------| +| .101 | eero node | 64:da:ed:29:12:ad | +| .216 | eero node | 64:da:ed:29:2e:8d | +| .227 | eero node | 64:da:ed:1c:b5:6d | + +**ESP/Raspberry Pi Devices:** +| IP | Hostname | MAC Address | Purpose | +|----|----------|-------------|---------| +| .81 | wlan0 | 70:89:76:ba:0f:d4 | Unknown Pi | +| .90 | ESP_C1DDAA | 84:f3:eb:c1:dd:aa | ESPHome device | +| .171 | raspberrypi | b8:27:eb:a9:03:66 | Unknown | +| .207 | esphome-web-055c68 | 6c:c8:40:05:5c:68 | ESPHome device | +| .246 | raspberrypi | b8:27:eb:fc:56:33 | Unknown | + +**Mobile Devices:** Various iPhones, iPads, Watches in DHCP pool (transient) + +--- + +## External Infrastructure + +### VPS (Hudson Valley Host) +| IP | Hostname | Service | +|----|----------|---------| +| 66.63.182.168 | vps.nianticbooks.com | Caddy Reverse Proxy | + +### Gaming VPS (deadeyeg4ming.vip) +| IP | Hostname | Service | +|----|----------|---------| +| 51.222.12.162 | deadeyeg4ming.vip | WireGuard Server (unlimited bandwidth) | + +### WireGuard Tunnel (10.0.9.0/24) +| IP | Endpoint | Role | +|----|----------|------| +| 10.0.9.1 | Gaming VPS | WireGuard Server | +| 10.0.9.2 | UCG Ultra | WireGuard Client | +| 10.0.9.3 | VPS Proxy | Internal proxy IP (used by Caddy) | + +--- + +## Public Domain Routes (via Caddy on VPS) + +| Domain | Backend | Status | +|--------|---------|--------| +| freddesk.nianticbooks.com | 10.0.10.3:8006 | Active | +| ad5m.nianticbooks.com | 10.0.10.30:80 | Active | +| bob.nianticbooks.com | 10.0.10.24:8123 | Active | +| auth.nianticbooks.com | 10.0.10.21:9000 | Active | +| cocktails.nianticbooks.com | 10.0.10.40 | Active | +| tasks.nianticbooks.com | 10.0.10.27:3456 | Active (Vikunja - no longer actively used) | + +## Internal HTTPS Routes (via Caddy Internal Proxy on CT 127) + +| Domain | Backend | Purpose | Certificate | +|--------|---------|---------|-------------| +| sonarr.nianticbooks.home | 10.0.10.27:8989 | TV automation | Caddy Internal PKI | +| radarr.nianticbooks.home | 10.0.10.27:7878 | Movie automation | Caddy Internal PKI | +| prowlarr.nianticbooks.home | 10.0.10.27:9696 | Indexer manager | Caddy Internal PKI | +| bazarr.nianticbooks.home | 10.0.10.27:6767 | Subtitle automation | Caddy Internal PKI | +| deluge.nianticbooks.home | 10.0.10.27:8112 | BitTorrent client | Caddy Internal PKI | +| calibre.nianticbooks.home | 10.0.10.27:8083 | eBook library | Caddy Internal PKI | +| vikunja.nianticbooks.home | 10.0.10.27:3456 | Task management (deprecated) | Caddy Internal PKI | +| dockge.nianticbooks.home | 10.0.10.27:5001 | Docker stack mgmt | Caddy Internal PKI | + +--- + +## Container/VM Quick Reference + +### main-pve (10.0.10.3) +| CT ID | Name | IP | +|-------|------|-----| +| 102 | postgresql | 10.0.10.20 | +| 103 | bar-assistant | 10.0.10.40 | +| 105 | pterodactyl-panel | 10.0.10.45 | +| 106 | n8n | 10.0.10.22 | +| 107 | pterodactyl-wings | 10.0.10.46 | +| 115 | ca-server | 10.0.10.15 | +| 121 | authentik | 10.0.10.21 | +| 123 | rustdesk | 10.0.10.23 | +| 125 | prometheus | 10.0.10.25 | +| 127 | dockge | 10.0.10.27 | +| 128 | uptime-kuma | 10.0.10.26 | +| 130 | openclaw | 10.0.10.28 | +| 131 | minecraft-forge | 10.0.10.41 | +| 132 | minecraft-stoneblock4 | 10.0.10.42 | +| 135 | vehicle-tracker | 10.0.10.35 | + +### pve-router (10.0.10.2) +| ID | Name | IP | +|----|------|-----| +| VM 104 | haos16.2 (Home Assistant) | 10.0.10.24 | +| CT 101 | twingate-connector | 10.0.10.179 | + +### pve-storage (10.0.10.4) +| ID | Name | IP | +|----|------|-----| +| VM 400 | OMV | 10.0.10.5 | + +--- + +## Deprecated/Removed + +| Date | Item | Reason | +|------|------|--------| +| 2026-01-13 | CT 100 pve-scripts-local | Unused experiment, caused IP conflict with bar-assistant | +| - | 10.0.10.71 spoolman | Bambu printer incompatible | +| - | 10.0.10.112 authelia | Failed experiment | + +--- + +## Audit History + +| Date | Action | Notes | +|------|--------|-------| +| 2026-01-13 | Full network audit | Compared UCG DHCP export vs documentation, verified all running services | +| 2026-01-13 | Removed CT 100 | pve-scripts-local on pve-router - IP conflict resolved | +| 2025-12-29 | Initial audit | Infrastructure audit template completed | diff --git a/infrastructure/LOCAL-CA-SETUP.md b/infrastructure/LOCAL-CA-SETUP.md new file mode 100644 index 0000000..c074261 --- /dev/null +++ b/infrastructure/LOCAL-CA-SETUP.md @@ -0,0 +1,460 @@ +# Local Certificate Authority Setup + +This document describes the setup of a local Certificate Authority (CA) for issuing HTTPS certificates to internal services. + +## Overview + +**Problem:** Internal services (Home Assistant, AD5M printer, Dockge, etc.) use HTTP, causing: +- Browser security warnings +- Some services refuse to work without HTTPS +- Insecure credential transmission + +**Solution:** Local CA using `step-ca` that issues trusted certificates for `.nianticbooks.home` domain. + +## Architecture + +### CA Server Location +- **Host:** main-pve (10.0.10.3) - most reliable server +- **Container:** LXC container for isolation +- **IP:** 10.0.10.15 (reserved for ca-server) +- **Domain:** ca.nianticbooks.home + +### Services Requiring Certificates + +| Service | Current IP | Domain | Port | +|---------|-----------|---------|------| +| Home Assistant | 10.0.10.24 | bob.nianticbooks.home | 8123 | +| AD5M Printer | 10.0.10.30 | ad5m.nianticbooks.home | 80 | +| Dockge | 10.0.10.27 | dockge.nianticbooks.home | 5001 | +| Proxmox (main-pve) | 10.0.10.3 | freddesk.nianticbooks.home | 8006 | +| Proxmox (pve-router) | 10.0.10.2 | pve-router.nianticbooks.home | 8006 | +| OMV | 10.0.10.5 | omv.nianticbooks.home | 80 | + +## Implementation Plan + +### Phase 1: CA Server Setup + +1. Create LXC container on main-pve +2. Install step-ca +3. Initialize CA with: + - CA name: "Homelab Internal CA" + - Domain: nianticbooks.home + - Validity: 10 years (root), 1 year (leaf certificates) + +### Phase 2: Certificate Generation + +For each service, generate: +- Server certificate +- Private key +- Auto-renewal script + +### Phase 3: Service Configuration + +Configure each service to: +- Use generated certificate +- Listen on HTTPS port +- Optionally redirect HTTP โ†’ HTTPS + +### Phase 4: Client Trust + +Distribute root CA certificate to: +- Linux clients (this computer, servers) +- Windows clients +- macOS clients +- Mobile devices (iOS/Android) + +## Step-by-Step Implementation + +### 1. Create CA Server Container + +```bash +# On main-pve (10.0.10.3) +pct create 115 local:vztmpl/ubuntu-22.04-standard_22.04-1_amd64.tar.zst \ + --hostname ca-server \ + --cores 1 \ + --memory 512 \ + --swap 512 \ + --storage local-lvm \ + --rootfs 8 \ + --net0 name=eth0,bridge=vmbr0,ip=10.0.10.15/24,gw=10.0.10.1 \ + --unprivileged 1 \ + --features nesting=1 \ + --onboot 1 + +# Start container +pct start 115 + +# Enter container +pct enter 115 +``` + +### 2. Install step-ca + +```bash +# Inside container +apt update && apt install -y wget + +# Download step-ca +wget https://dl.smallstep.com/gh-release/cli/gh-release-header/v0.25.0/step-cli_0.25.0_amd64.deb +wget https://dl.smallstep.com/gh-release/certificates/gh-release-header/v0.25.0/step-ca_0.25.0_amd64.deb + +# Install +dpkg -i step-cli_0.25.0_amd64.deb step-ca_0.25.0_amd64.deb + +# Verify +step version +step-ca version +``` + +### 3. Initialize CA + +```bash +# Create CA user +useradd -r -s /bin/bash -m -d /etc/step-ca step + +# Initialize CA (as step user) +su - step +step ca init + +# Configuration prompts: +# Name: Homelab Internal CA +# DNS: ca.nianticbooks.home +# Address: :443 +# Provisioner: admin@nianticbooks.home +# Password: [generate strong password, save to password manager] +``` + +### 4. Configure CA Service + +```bash +# Create systemd service +cat > /etc/systemd/system/step-ca.service <<'EOF' +[Unit] +Description=step-ca Certificate Authority +After=network.target + +[Service] +Type=simple +User=step +Group=step +WorkingDirectory=/etc/step-ca +ExecStart=/usr/bin/step-ca /etc/step-ca/config/ca.json --password-file=/etc/step-ca/password.txt +Restart=on-failure +RestartSec=10 + +[Install] +WantedBy=multi-user.target +EOF + +# Store CA password securely +echo "YOUR_CA_PASSWORD" > /etc/step-ca/password.txt +chown step:step /etc/step-ca/password.txt +chmod 600 /etc/step-ca/password.txt + +# Enable and start +systemctl daemon-reload +systemctl enable step-ca +systemctl start step-ca +systemctl status step-ca +``` + +### 5. Issue Certificates for Services + +#### Home Assistant (bob.nianticbooks.home) + +```bash +# Generate certificate +step certificate create bob.nianticbooks.home \ + bob.crt bob.key \ + --profile leaf \ + --not-after 8760h \ + --ca /etc/step-ca/certs/intermediate_ca.crt \ + --ca-key /etc/step-ca/secrets/intermediate_ca_key \ + --bundle + +# Copy to Home Assistant +scp bob.crt bob.key homeassistant.local:/ssl/ +``` + +Configure Home Assistant (`configuration.yaml`): +```yaml +http: + ssl_certificate: /ssl/bob.crt + ssl_key: /ssl/bob.key + server_port: 8123 +``` + +#### AD5M Printer (Fluidd/Klipper) + +```bash +step certificate create ad5m.nianticbooks.home \ + ad5m.crt ad5m.key \ + --profile leaf \ + --not-after 8760h \ + --ca /etc/step-ca/certs/intermediate_ca.crt \ + --ca-key /etc/step-ca/secrets/intermediate_ca_key \ + --bundle + +# Copy to printer +scp ad5m.crt ad5m.key root@10.0.10.30:/etc/nginx/ssl/ +``` + +Configure nginx for Fluidd: +```nginx +server { + listen 443 ssl; + server_name ad5m.nianticbooks.home; + + ssl_certificate /etc/nginx/ssl/ad5m.crt; + ssl_certificate_key /etc/nginx/ssl/ad5m.key; + + location / { + proxy_pass http://localhost:80; + } +} +``` + +#### Dockge + +```bash +step certificate create dockge.nianticbooks.home \ + dockge.crt dockge.key \ + --profile leaf \ + --not-after 8760h \ + --ca /etc/step-ca/certs/intermediate_ca.crt \ + --ca-key /etc/step-ca/secrets/intermediate_ca_key \ + --bundle + +# Copy to Dockge host +``` + +#### Proxmox + +```bash +# Main PVE +step certificate create freddesk.nianticbooks.home \ + freddesk.crt freddesk.key \ + --profile leaf \ + --not-after 8760h \ + --ca /etc/step-ca/certs/intermediate_ca.crt \ + --ca-key /etc/step-ca/secrets/intermediate_ca_key \ + --bundle + +# Copy to Proxmox +scp freddesk.crt freddesk.key root@10.0.10.3:/etc/pve/local/pveproxy-ssl.pem +scp freddesk.key root@10.0.10.3:/etc/pve/local/pveproxy-ssl.key + +# Restart Proxmox web service +ssh root@10.0.10.3 "systemctl restart pveproxy" +``` + +### 6. Certificate Auto-Renewal + +Create renewal script on CA server: + +```bash +cat > /usr/local/bin/renew-certs.sh <<'EOF' +#!/bin/bash +# Auto-renew certificates for homelab services + +SERVICES=( + "bob.nianticbooks.home:10.0.10.24:/ssl/" + "ad5m.nianticbooks.home:10.0.10.30:/etc/nginx/ssl/" + "dockge.nianticbooks.home:10.0.10.27:/etc/ssl/" + "freddesk.nianticbooks.home:10.0.10.3:/etc/pve/local/" +) + +for service in "${SERVICES[@]}"; do + IFS=':' read -r domain ip path <<< "$service" + + echo "Renewing certificate for $domain..." + + # Check if certificate expires in < 30 days + if step certificate needs-renewal "${domain}.crt"; then + # Generate new certificate + step certificate create "$domain" \ + "${domain}.crt" "${domain}.key" \ + --profile leaf \ + --not-after 8760h \ + --ca /etc/step-ca/certs/intermediate_ca.crt \ + --ca-key /etc/step-ca/secrets/intermediate_ca_key \ + --bundle --force + + # Deploy to service + scp "${domain}.crt" "${domain}.key" "root@${ip}:${path}" + + # Restart service + case "$domain" in + bob.*) + ssh root@${ip} "systemctl restart home-assistant@homeassistant" + ;; + ad5m.*) + ssh root@${ip} "systemctl restart nginx" + ;; + freddesk.*) + ssh root@${ip} "systemctl restart pveproxy" + ;; + esac + + echo "โœ“ Renewed $domain" + fi +done +EOF + +chmod +x /usr/local/bin/renew-certs.sh + +# Add to crontab (weekly check) +echo "0 2 * * 0 /usr/local/bin/renew-certs.sh" >> /etc/crontab +``` + +### 7. Client Trust Configuration + +#### Linux (Ubuntu/Debian) + +```bash +# Copy root CA certificate +scp root@10.0.10.15:/etc/step-ca/certs/root_ca.crt /usr/local/share/ca-certificates/homelab-ca.crt + +# Update trust store +update-ca-certificates + +# Verify +curl https://bob.nianticbooks.home:8123 +``` + +#### Windows + +1. Copy `root_ca.crt` to Windows machine +2. Right-click โ†’ Install Certificate +3. Store Location: Local Machine +4. Certificate Store: Trusted Root Certification Authorities +5. Click Next โ†’ Finish + +#### macOS + +```bash +# Copy certificate +scp root@10.0.10.15:/etc/step-ca/certs/root_ca.crt ~/Downloads/ + +# Import to keychain +sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ~/Downloads/root_ca.crt +``` + +#### iOS/Android + +1. Email `root_ca.crt` to device or host on web server +2. Open certificate file on device +3. Install profile +4. iOS: Settings โ†’ General โ†’ About โ†’ Certificate Trust Settings โ†’ Enable +5. Android: Settings โ†’ Security โ†’ Install from storage + +## DNS Configuration + +Ensure UCG Ultra has DNS entries for all services: + +``` +bob.nianticbooks.home โ†’ 10.0.10.24 +ad5m.nianticbooks.home โ†’ 10.0.10.30 +dockge.nianticbooks.home โ†’ 10.0.10.27 +freddesk.nianticbooks.home โ†’ 10.0.10.3 +pve-router.nianticbooks.home โ†’ 10.0.10.2 +omv.nianticbooks.home โ†’ 10.0.10.5 +ca.nianticbooks.home โ†’ 10.0.10.15 +``` + +## Verification + +Test each service: + +```bash +# Home Assistant +curl -I https://bob.nianticbooks.home:8123 + +# AD5M +curl -I https://ad5m.nianticbooks.home + +# Dockge +curl -I https://dockge.nianticbooks.home:5001 + +# Proxmox +curl -I https://freddesk.nianticbooks.home:8006 +``` + +## Troubleshooting + +### Certificate Not Trusted + +```bash +# Check if CA is in trust store +ls /usr/local/share/ca-certificates/ + +# Verify certificate chain +openssl s_client -connect bob.nianticbooks.home:8123 -showcerts + +# Check certificate validity +openssl x509 -in bob.crt -text -noout +``` + +### Service Won't Start with HTTPS + +```bash +# Check certificate permissions +ls -la /ssl/ + +# Check service logs +journalctl -u home-assistant@homeassistant -f + +# Verify certificate matches key +openssl x509 -noout -modulus -in cert.crt | openssl md5 +openssl rsa -noout -modulus -in cert.key | openssl md5 +# Should match +``` + +## Maintenance + +### Certificate Expiry Monitoring + +```bash +# Check certificate expiration +for cert in *.crt; do + echo "$cert:" + openssl x509 -in "$cert" -noout -dates +done +``` + +### Renewing Root CA + +Root CA expires in 10 years. To renew: + +1. Generate new root CA +2. Issue new intermediate CA +3. Re-issue all service certificates +4. Update all client trust stores + +## Security Considerations + +- CA private key stored encrypted on CA server +- CA server only accessible from local network +- Certificate validity limited to 1 year +- Auto-renewal prevents expiry +- Root CA backed up to OMV storage + +## Backup + +```bash +# Backup CA configuration +rsync -av /etc/step-ca/ /mnt/omv-backup/ca-backup/$(date +%Y%m%d)/ + +# Store root CA password in password manager +``` + +## Next Steps + +- [ ] Create CA server LXC container +- [ ] Install and configure step-ca +- [ ] Generate certificates for all services +- [ ] Configure services to use HTTPS +- [ ] Distribute root CA to all devices +- [ ] Set up auto-renewal +- [ ] Test all services with HTTPS +- [ ] Update Pangolin routes to use HTTPS backend diff --git a/infrastructure/LOCAL-HTTPS-QUICKSTART.md b/infrastructure/LOCAL-HTTPS-QUICKSTART.md new file mode 100644 index 0000000..9448a21 --- /dev/null +++ b/infrastructure/LOCAL-HTTPS-QUICKSTART.md @@ -0,0 +1,361 @@ +# Local HTTPS Quick Start Guide + +Get all your homelab services running on HTTPS in ~30 minutes. + +## What This Does + +Converts all your internal services from HTTP to HTTPS: +- โœ… Home Assistant (bob.nianticbooks.home) +- โœ… AD5M 3D Printer (ad5m.nianticbooks.home) +- โœ… Dockge (dockge.nianticbooks.home) +- โœ… Proxmox Web UI (freddesk.nianticbooks.home) +- โœ… OpenMediaVault (omv.nianticbooks.home) + +## Prerequisites + +- SSH access to main-pve (10.0.10.3) as root +- DNS entries configured in UCG Ultra for all services +- ~2GB free space on main-pve + +## Step-by-Step Instructions + +### 1. Set Up Certificate Authority (10 min) + +SSH into main-pve and run the setup script: + +```bash +# From this computer +scp ~/projects/infrastructure/scripts/setup-local-ca.sh root@10.0.10.3:/root/ + +# SSH into main-pve +ssh root@10.0.10.3 + +# Run setup +cd /root +chmod +x setup-local-ca.sh +./setup-local-ca.sh +``` + +**What it does:** +- Creates LXC container at 10.0.10.15 +- Installs step-ca (Certificate Authority software) +- Initializes CA with 10-year root certificate +- Starts CA service + +**You'll be prompted for:** +- CA password (save this in your password manager!) +- Container root password + +### 2. Issue Certificates for All Services (5 min) + +Still on main-pve: + +```bash +# Copy and run certificate generation script +scp ~/projects/infrastructure/scripts/issue-service-certs.sh root@10.0.10.3:/root/ + +# On main-pve +chmod +x issue-service-certs.sh +./issue-service-certs.sh +``` + +**What it does:** +- Generates SSL certificates for all services +- Saves them to `/tmp/certs/` on main-pve + +**Output:** +``` +/tmp/certs/ +โ”œโ”€โ”€ bob.nianticbooks.home.crt +โ”œโ”€โ”€ bob.nianticbooks.home.key +โ”œโ”€โ”€ ad5m.nianticbooks.home.crt +โ”œโ”€โ”€ ad5m.nianticbooks.home.key +โ””โ”€โ”€ ... (other services) +``` + +### 3. Configure Each Service (15 min) + +#### Home Assistant (bob.nianticbooks.home) + +```bash +# On main-pve, copy certs to Home Assistant +scp /tmp/certs/bob.nianticbooks.home.* root@10.0.10.24:/config/ssl/ + +# SSH into Home Assistant +ssh root@10.0.10.24 + +# Edit configuration +nano /config/configuration.yaml +``` + +Add/update: +```yaml +http: + ssl_certificate: /config/ssl/bob.nianticbooks.home.crt + ssl_key: /config/ssl/bob.nianticbooks.home.key + server_port: 8123 +``` + +Restart Home Assistant: +```bash +# From HA CLI +ha core restart + +# Or restart container/VM from Proxmox +``` + +Test: https://bob.nianticbooks.home:8123 + +#### AD5M Printer (Fluidd/Klipper) + +```bash +# Copy certs to printer +scp /tmp/certs/ad5m.nianticbooks.home.* root@10.0.10.30:/etc/nginx/ssl/ + +# SSH into printer +ssh root@10.0.10.30 + +# Create nginx HTTPS config +cat > /etc/nginx/sites-available/fluidd-https <<'EOF' +server { + listen 443 ssl http2; + server_name ad5m.nianticbooks.home; + + ssl_certificate /etc/nginx/ssl/ad5m.nianticbooks.home.crt; + ssl_certificate_key /etc/nginx/ssl/ad5m.nianticbooks.home.key; + + # SSL configuration + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + + location / { + proxy_pass http://127.0.0.1:80; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # WebSocket support for Moonraker + location /websocket { + proxy_pass http://127.0.0.1:7125/websocket; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header Host $host; + } +} + +# Redirect HTTP to HTTPS +server { + listen 80; + server_name ad5m.nianticbooks.home; + return 301 https://$server_name$request_uri; +} +EOF + +# Enable site +ln -sf /etc/nginx/sites-available/fluidd-https /etc/nginx/sites-enabled/ +nginx -t +systemctl restart nginx +``` + +Test: https://ad5m.nianticbooks.home + +Update printer UI setting to: `https://ad5m.nianticbooks.home/fluidd` + +#### Dockge + +```bash +# Copy certs +scp /tmp/certs/dockge.nianticbooks.home.* root@10.0.10.27:/opt/dockge/ssl/ + +# Edit docker-compose.yml to add SSL +``` + +Add environment variables: +```yaml +environment: + - SSL_KEY=/app/ssl/dockge.nianticbooks.home.key + - SSL_CERT=/app/ssl/dockge.nianticbooks.home.crt +``` + +Or use nginx reverse proxy (recommended). + +#### Proxmox (freddesk.nianticbooks.home) + +```bash +# On main-pve +cp /tmp/certs/freddesk.nianticbooks.home.crt /etc/pve/local/pveproxy-ssl.pem +cp /tmp/certs/freddesk.nianticbooks.home.key /etc/pve/local/pveproxy-ssl.key + +# Restart Proxmox web service +systemctl restart pveproxy +``` + +Test: https://freddesk.nianticbooks.home:8006 + +#### OpenMediaVault + +```bash +# Copy certs to OMV +scp /tmp/certs/omv.nianticbooks.home.* root@10.0.10.5:/etc/ssl/ + +# Configure via OMV Web UI: +# System โ†’ Certificates โ†’ SSL โ†’ Import +# Then: System โ†’ General Settings โ†’ Enable SSL/TLS, select certificate +``` + +### 4. Trust the CA on Your Devices (5 min) + +#### This Computer (Linux) + +```bash +cd ~/projects/infrastructure/scripts +./trust-ca-client.sh +``` + +#### Other Linux Computers + +```bash +# Copy script and run +scp scripts/trust-ca-client.sh user@other-computer:/tmp/ +ssh user@other-computer "bash /tmp/trust-ca-client.sh" +``` + +#### Windows + +1. Copy root CA from main-pve: + ```bash + scp root@10.0.10.3:/tmp/homelab-root-ca.crt ~/Downloads/ + ``` + +2. On Windows: + - Copy file to Windows machine + - Right-click โ†’ Install Certificate + - Store Location: **Local Machine** + - Certificate Store: **Trusted Root Certification Authorities** + - Click Finish + +#### macOS + +```bash +# Copy certificate +scp root@10.0.10.3:/tmp/homelab-root-ca.crt ~/Downloads/ + +# Install (will prompt for password) +sudo security add-trusted-cert -d -r trustRoot \ + -k /Library/Keychains/System.keychain \ + ~/Downloads/homelab-root-ca.crt +``` + +#### iOS + +1. Email the certificate to yourself or host on a web server +2. Open the certificate file on iOS device +3. Settings โ†’ General โ†’ VPN & Device Management โ†’ Install profile +4. Settings โ†’ General โ†’ About โ†’ Certificate Trust Settings +5. Enable full trust for "Homelab Internal CA" + +#### Android + +1. Transfer certificate to device +2. Settings โ†’ Security โ†’ Install from storage +3. Select the certificate file +4. Name it "Homelab CA" + +## Verification + +Test all services with HTTPS: + +```bash +# From any trusted computer +curl -I https://bob.nianticbooks.home:8123 +curl -I https://ad5m.nianticbooks.home +curl -I https://freddesk.nianticbooks.home:8006 +curl -I https://omv.nianticbooks.home +``` + +Open in browser - you should see **no certificate warnings**. + +## Troubleshooting + +### Certificate not trusted + +```bash +# Check if CA is installed +ls /usr/local/share/ca-certificates/ | grep homelab + +# Re-run trust script +cd ~/projects/infrastructure/scripts +./trust-ca-client.sh +``` + +### Service won't start with HTTPS + +```bash +# Check certificate files exist +ls -la /path/to/ssl/ + +# Check permissions +chmod 644 /path/to/cert.crt +chmod 600 /path/to/cert.key + +# Check service logs +journalctl -u service-name -f +``` + +### Certificate expired (after 1 year) + +```bash +# Re-run certificate issuance +ssh root@10.0.10.3 +./issue-service-certs.sh + +# Re-deploy to services +``` + +## Certificate Renewal + +Certificates are valid for 1 year. Set a calendar reminder to renew them annually: + +```bash +# On main-pve +./issue-service-certs.sh +# Then re-deploy to each service +``` + +Or set up auto-renewal (see full documentation in LOCAL-CA-SETUP.md). + +## Next Steps + +Once HTTPS is working: + +1. Update Pangolin routes on VPS to use HTTPS backends +2. Configure HTTP โ†’ HTTPS redirects +3. Update bookmarks to use `https://` +4. Update Home Assistant app to use HTTPS URL + +## Files Created + +- `/tmp/homelab-root-ca.crt` - Root CA certificate (install on all devices) +- `/tmp/certs/*.crt` - Service certificates +- `/tmp/certs/*.key` - Private keys (keep secure!) + +## Security Notes + +- Root CA is valid for 10 years +- Service certificates valid for 1 year +- Private keys stored only on CA server and target services +- CA server only accessible from local network (10.0.10.0/24) + +## Support + +Full documentation: `LOCAL-CA-SETUP.md` +Troubleshooting: Check service logs and certificate validity + +--- + +**Time Estimate:** 30-45 minutes for complete setup +**Difficulty:** Intermediate +**Impact:** All services accessible via trusted HTTPS diff --git a/infrastructure/MIGRATION-CHECKLIST.md b/infrastructure/MIGRATION-CHECKLIST.md new file mode 100644 index 0000000..a8bbaba --- /dev/null +++ b/infrastructure/MIGRATION-CHECKLIST.md @@ -0,0 +1,554 @@ +# IP Migration Checklist + +**Date Started:** _______________ +**Estimated Completion:** _______________ +**Status:** Not Started + +--- + +## Pre-Migration Tasks + +### Backup Current Configuration +- [ ] Export current DHCP leases from UCG Ultra (โœ… Already done: dhcp-export-all-2025-11-14T22-55-18.871Z.csv) +- [ ] Screenshot current UCG Ultra network settings +- [ ] Backup Pangolin reverse proxy configuration on VPS +- [ ] Document current Proxmox VM network configs + +### Testing Preparation +- [ ] Verify SSH access to all Proxmox nodes +- [ ] Verify access to UCG Ultra web UI +- [ ] Have physical access to at least one machine (if remote access breaks) +- [ ] Note current Pangolin routes and test URLs + +--- + +## Phase 1: Update UCG Ultra DHCP Pool โœ… COMPLETED + +**Completion Date:** 2025-12-11 +**Status:** โœ… Verified correct configuration + +### Steps: +1. [x] Log into UCG Ultra web interface +2. [x] Navigate to Settings โ†’ Networks โ†’ Default (LAN) +3. [x] Find DHCP settings +4. [x] DHCP range verified: `10.0.10.50-10.0.10.254` โœ… + - Static/Reserved range: 10.0.10.1-49 (infrastructure) + - Dynamic DHCP pool: 10.0.10.50-254 (clients/devices) +5. [x] Configuration correct - no changes needed +6. [x] Verified: All services functioning, no connectivity issues + +**Notes:** DHCP range was already correctly configured. All static reservations in 10.0.10.1-49 range working as expected. + +--- + +## Phase 2: Update Existing DHCP Reservations โœ… COMPLETED + +**Completion Date:** 2025-12-11 +**Actual Time:** 15 minutes +**Status:** โœ… All devices responding at new IPs + +### 2.1 Update HOMELAB-COMMAND โœ… +- [x] Current IP: 10.0.10.92 +- [x] Target IP: 10.0.10.10 +- [x] MAC: 90:de:80:80:e7:04 +- [x] Updated reservation in UCG Ultra +- [x] Renewed DHCP lease +- [x] Verified connectivity: Responding at 10.0.10.10 โœ… + +### 2.2 Update HP iLO โœ… +- [x] Current IP: 10.0.10.53 +- [x] Target IP: 10.0.10.13 +- [x] MAC: b4:b5:2f:ea:8c:30 +- [x] Updated reservation in UCG Ultra +- [x] Device responded to lease renewal +- [x] Verified: Accessible at https://10.0.10.13 โœ… + +### 2.3 Update ad5m (3D Printer) โœ… +- [x] Current IP: 10.0.10.189 +- [x] Target IP: 10.0.10.30 +- [x] MAC: 88:a9:a7:99:c3:64 +- [x] Updated reservation in UCG Ultra +- [x] Printer rebooted +- [x] Verified: Accessible at http://10.0.10.30 โœ… +- [x] Updated Caddy route: ad5m.nianticbooks.com โ†’ 10.0.10.30:80 +- [x] Tested: https://ad5m.nianticbooks.com working โœ… + +--- + +## Phase 3: Create New DHCP Reservations for VMs โœ… COMPLETED + +**Completion Date:** 2025-12-11 +**Actual Time:** 30 minutes +**Status:** โœ… All VMs responding at new IPs + +### 3.1 OpenMediaVault โœ… +- [x] Current IP: 10.0.10.178 +- [x] Target IP: 10.0.10.5 +- [x] MAC: bc:24:11:a8:ff:0b +- [x] Created reservation in UCG Ultra +- [x] Networking restarted +- [x] Verified: Accessible at http://10.0.10.5 โœ… + +### 3.2 Home Assistant โœ… +- [x] Current IP: 10.0.10.194 +- [x] Target IP: 10.0.10.24 +- [x] MAC: 02:f5:e9:54:36:28 +- [x] Created reservation in UCG Ultra +- [x] VM restarted +- [x] Verified: Accessible at http://10.0.10.24:8123 โœ… +- [x] Updated Caddy route: bob.nianticbooks.com โ†’ 10.0.10.24:8123 +- [x] Tested: https://bob.nianticbooks.com working โœ… + +### 3.3 Dockge โœ… +- [x] Current IP: 10.0.10.104 +- [x] Target IP: 10.0.10.27 +- [x] MAC: bc:24:11:4a:42:07 +- [x] Created reservation in UCG Ultra +- [x] VM restarted +- [x] Verified: Accessible at 10.0.10.27 โœ… + +### 3.4 ESPHome โœ… +- [x] ~~Removed~~ - ESPHome now runs as Home Assistant add-on (no separate VM needed) +- [x] Container 102 deleted from pve-router +- [x] IP 10.0.10.28 released (available for other use) + +### 3.5 Docker Host โœ… +- [x] Current IP: 10.0.10.108 +- [x] Target IP: 10.0.10.29 +- [x] MAC: bc:24:11:a8:ff:0b +- [x] Created reservation in UCG Ultra +- [x] VM restarted +- [x] Verified: All containers running at 10.0.10.29 โœ… + +### 3.6 pve-scripts-local โœ… +- [x] Current IP: 10.0.10.79 +- [x] Target IP: 10.0.10.40 +- [x] MAC: bc:24:11:0f:78:84 +- [x] Created reservation in UCG Ultra +- [x] VM restarted +- [x] Verified: Scripts functional at 10.0.10.40 โœ… + +--- + +## Phase 4: Update Pangolin Reverse Proxy Routes โœ… COMPLETED + +**Completion Date:** 2025-12-13 +**Actual Time:** ~20 minutes +**Status:** โœ… All routes operational (Note: Completed as part of Phase 5 with Caddy) + +### 4.1 Backup Pangolin Configuration +- [x] Pangolin replaced with Caddy reverse proxy (simpler configuration) +- [x] Caddy configuration at /etc/caddy/Caddyfile on VPS + +### 4.2 Update Routes โœ… +- [x] Caddy routes configured: + ``` + freddesk.nianticbooks.com โ†’ 10.0.10.3:8006 (main-pve Proxmox) + ad5m.nianticbooks.com โ†’ 10.0.10.30:80 (Prusa 3D printer) + bob.nianticbooks.com โ†’ 10.0.10.24:8123 (Home Assistant) + ``` +- [x] Deprecated spools.nianticbooks.com route not included +- [x] Caddy service running and enabled + +### 4.3 Verify Routes โœ… +- [x] Test freddesk: https://freddesk.nianticbooks.com โœ… Working +- [x] Test ad5m: https://ad5m.nianticbooks.com โœ… Working +- [x] Test bob: https://bob.nianticbooks.com โœ… Working (after HA config fix) + +### 4.4 Additional Configuration โœ… +- [x] Fixed Home Assistant trusted_proxies configuration +- [x] Added 10.0.8.1 (VPS WireGuard IP) to Home Assistant trusted_proxies +- [x] Home Assistant now accepts requests from bob.nianticbooks.com + +**Notes:** +- Switched from Pangolin (Gerbil-based) to Caddy for simpler configuration +- Caddy provides automatic HTTPS via Let's Encrypt +- Home Assistant required `trusted_proxies` configuration to accept external domain +- All public services verified functional on 2025-12-13 + +--- + +## Phase 5: Configure WireGuard Tunnel โœ… COMPLETED + +**Completion Date:** 2025-12-11 +**Actual Time:** ~2 hours +**Status:** โœ… Operational + +### 5.1 Install WireGuard on VPS โœ… +- [x] SSH to VPS: `ssh fred@66.63.182.168` +- [x] Install WireGuard: Already installed (wireguard-tools v1.0.20210914) +- [x] Enable IP forwarding: `sudo sysctl -w net.ipv4.ip_forward=1` +- [x] Make persistent: Added to /etc/sysctl.conf +- [x] Generate server keys: Created successfully +- [x] VPS Server Public Key: `8jcW7SyId/79Jg4+t0Qd0DaDA+4B+GQf14FRR2TXFRE=` + +### 5.2 Configure WireGuard on VPS โœ… +- [x] Created config: /etc/wireguard/wg0.conf +- [x] Tunnel subnet: 10.0.8.0/24 (VPS: 10.0.8.1, UCG Ultra: 10.0.8.2) +- [x] Configured NAT and forwarding rules +- [x] Started WireGuard: `sudo systemctl start wg-quick@wg0` +- [x] Enabled on boot: `sudo systemctl enable wg-quick@wg0` +- [x] Verified: `sudo wg show` - peer connected with active handshake + +### 5.3 Configure WireGuard on UCG Ultra โœ… +- [x] Logged into UCG Ultra web interface (10.0.10.1) +- [x] Navigated to: Settings โ†’ Teleport & VPN โ†’ VPN Client +- [x] Created WireGuard VPN Client +- [x] Configured client settings: + - Server: 66.63.182.168:51820 + - VPS Public Key: 8jcW7SyId/79Jg4+t0Qd0DaDA+4B+GQf14FRR2TXFRE= + - Client Address: 10.0.8.2/24 + - Persistent Keepalive: 25 seconds +- [x] UCG Ultra Client Public Key: `KJOj35HdntdLHQTU0tfNPJ/x1GD9SlNy78GuMhMyzTg=` +- [x] Enabled and activated + +### 5.4 Test WireGuard Connectivity โœ… +- [x] From VPS, ping main-pve: โœ… Working (3/4 packets, ~12ms latency) +- [x] From VPS, HTTP to Home Assistant: โœ… Working (HTTP 405 response) +- [x] From VPS, ping 3D printer (10.0.10.30): โœ… Working +- [x] Tunnel stable with active handshake and data transfer + +### 5.5 Reverse Proxy Configuration โœ… +**Note:** Replaced Pangolin (Gerbil-based) with Caddy for simplicity + +- [x] Removed Pangolin and Traefik Docker containers +- [x] Installed Caddy reverse proxy +- [x] Created /etc/caddy/Caddyfile with routes: + - bob.nianticbooks.com โ†’ 10.0.10.24:8123 (Home Assistant) + - freddesk.nianticbooks.com โ†’ 10.0.10.3:8006 (Proxmox) + - ad5m.nianticbooks.com โ†’ 10.0.10.30:80 (Prusa 3D Printer) +- [x] Automatic HTTPS certificates obtained via Let's Encrypt +- [x] All public services verified working + +### 5.6 Public Service Verification โœ… +- [x] https://bob.nianticbooks.com - โœ… Working (Home Assistant) +- [x] https://freddesk.nianticbooks.com - โœ… Working (Proxmox) +- [x] https://ad5m.nianticbooks.com - โœ… Working (3D Printer) + +**Notes:** +- Tunnel endpoint: VPS 66.63.182.168:51820 โ†” UCG Ultra (home public IP) +- VPS can now reach all 10.0.10.0/24 services through tunnel +- Caddy provides automatic HTTPS and simpler configuration than Pangolin +- No rollback needed - system is stable and operational + +--- + +## Phase 6: Deploy New Services (After WireGuard Active) + +**Estimated Time:** Variable (each service 1-2 hours) +**Risk Level:** Low (new services, nothing to break) + +### 6.1 PostgreSQL (10.0.10.20) โœ… COMPLETED +- [x] Create VM/container on main-pve +- [x] Assign static IP 10.0.10.20 in VM config +- [x] Install PostgreSQL (PostgreSQL 16) +- [x] Configure databases for: Authentik, n8n, RustDesk, Grafana +- [x] Test connectivity from other VMs +- [x] Verified: Responding at 10.0.10.20 โœ… + +### 6.2 Authentik SSO (10.0.10.21) โœ… COMPLETED +**Completion Date:** 2025-12-14 +**Actual Time:** ~3 hours +**Status:** โœ… Deployed and operational with Proxmox SSO + +- [x] Create VM/container on main-pve (Container ID: 121) +- [x] Assign static IP 10.0.10.21 (MAC: bc:24:11:de:18:41) +- [x] Install Authentik (via Docker Compose) +- [x] Configure PostgreSQL connection (using external DB at 10.0.10.20) +- [x] Add Caddy route: auth.nianticbooks.com โ†’ 10.0.10.21:9000 +- [x] Test: https://auth.nianticbooks.com โœ… Working +- [x] Complete initial setup and password change +- [x] Configure Proxmox OAuth2/OpenID integration โœ… +- [ ] Set up WebAuthn/FIDO2 (optional, future enhancement) +- [ ] Configure additional service integrations (n8n, Home Assistant, etc.) + +**Configuration Details:** +- Container: Debian 12 LXC (2 vCPUs, 4GB RAM, 20GB disk) +- Database: PostgreSQL on 10.0.10.20 (database: authentik, user: authentik) +- Secret Key: ZsJQbVLiCRtg23rEkXPuxIDJL5MxOxdQsf8ZJ+JHB9U= +- DB Password: authentik_password_8caaff5a73f9c66b +- Version: 2025.10.2 +- Automatic HTTPS via Let's Encrypt through Caddy +- Admin User: akadmin +- API Token: f7AsYT6FLZEWVvmN59lC0IQZfMLdgMniVPYhVwmYAFSKHez4aGxyn4Esm86r + +**Proxmox SSO Integration:** +- OAuth2 Provider: "Proxmox OpenID" (Client ID: proxmox) +- Client Secret: OAfAcjzzPDUnjEhaLVNIeNu1KR0Io06fB8kA8Np9DTgfgXcsLnN5DogrAfhk5zteazonVGcXfaESvf8viCQFVzq8wNVcp60Bo5D3xvfJ9ZjCzEMCQIljssbfr29zjsap +- Configured on all 3 Proxmox hosts: + - main-pve (10.0.10.3) โœ… + - gaming-pve (10.0.10.2) โœ… + - backup-pve (10.0.10.4) โœ… +- Scope mappings: openid, email, profile +- Login method: Click "Login with authentik" button on Proxmox login page +- Status: โœ… Working - seamless SSO authentication + +**Notes:** +- Using external PostgreSQL instead of bundled container for centralized database management +- Authentik SSO successfully integrated with all Proxmox hosts +- Users authenticate once to Authentik, then access all Proxmox hosts without re-authentication +- Documentation created: AUTHENTIK-SSO-GUIDE.md and AUTHENTIK-QUICK-START.md + +### 6.3 n8n (10.0.10.22) โœ… COMPLETED +- [x] Create VM/container on main-pve (Container ID: 106) +- [x] Assign static IP 10.0.10.22 +- [x] Install n8n (Docker-based deployment) +- [x] Configure PostgreSQL connection (using external DB at 10.0.10.20) +- [x] Updated to latest version (1.123.5) +- [x] Verified: Accessible at http://10.0.10.22:5678 โœ… +- [x] SSO Investigation: โŒ OIDC SSO requires n8n Enterprise license (not available in free self-hosted version) + +**Notes:** +- n8n OIDC/SSO is an Enterprise-only feature +- Free self-hosted version uses standard email/password authentication +- For SSO integration, would need n8n Cloud subscription or Enterprise license +- Current deployment uses regular authentication - fully functional + +### 6.4 n8n + Claude Code Integration โœ… COMPLETED +**Completion Date:** 2025-12-14 +**Actual Time:** ~2 hours +**Status:** โœ… Basic integration operational, ready for production workflows + +**Reference:** https://github.com/theNetworkChuck/n8n-claude-code-guide + +**Architecture:** +- n8n (10.0.10.22 on main-pve) โ†’ SSH โ†’ Claude Code (10.0.10.10 on HOMELAB-COMMAND) + +**Key Configuration Notes:** +- Windows SSH requires PowerShell as default shell for Claude Code to work +- SSH commands MUST use `-n` flag or "Disable Stdin" option to prevent hanging +- Claude Code headless mode: `--output-format json --permission-mode acceptEdits` +- Test workflow created and verified: "Claude Code Test" + +#### 6.4.1 Install Claude Code on HOMELAB-COMMAND (10.0.10.10) โœ… +- [x] SSH or RDP to HOMELAB-COMMAND (10.0.10.10) +- [x] Node.js already installed: v24.11.0 +- [x] Claude Code already installed: v2.0.65 +- [x] Verified installation: `claude --version` +- [x] Test headless mode: `claude -p "What is 2+2?" --output-format json --permission-mode acceptEdits` + +#### 6.4.2 Configure SSH Access for n8n โœ… +- [x] SSH server already running on HOMELAB-COMMAND (Windows OpenSSH) +- [x] Set PowerShell as default SSH shell (required for Claude Code): + ```powershell + New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -PropertyType String -Force + Restart-Service sshd + ``` +- [x] Generated SSH key on n8n VM: `ssh-keygen -t ed25519 -C "n8n-to-homelab-command"` +- [x] Added public key to HOMELAB-COMMAND: `C:\Users\Fred\.ssh\authorized_keys` +- [x] Test passwordless SSH: `ssh Fred@10.0.10.10 "hostname"` โœ… +- [x] Test Claude Code via SSH: `ssh -n Fred@10.0.10.10 "claude -p 'What is 2+2?' --output-format json --permission-mode acceptEdits"` โœ… +- [x] **Critical:** Must use `-n` flag with SSH to prevent stdin hanging + +#### 6.4.3 Configure n8n SSH Credentials โœ… +- [x] Logged into n8n web interface (http://10.0.10.22:5678) +- [x] Created SSH credential: **homelab-command-ssh** + - **Host:** 10.0.10.10 + - **Port:** 22 + - **Username:** Fred + - **Authentication:** Private Key (from `~/.ssh/id_ed25519` on n8n VM) +- [x] Connection tested successfully โœ… +- [x] Credential saved + +#### 6.4.4 Create Test Workflow โœ… +- [x] Created new workflow: "Claude Code Test" +- [x] Added **Manual Trigger** node +- [x] Added **SSH** node: + - **Credential:** homelab-command-ssh + - **Command:** `claude -p "What is 2+2?" --output-format json --permission-mode acceptEdits` + - **SSH Options:** Enabled "Disable Stdin" (equivalent to `-n` flag) +- [x] Added **Code** node to parse JSON response: + ```javascript + const sshOutput = $input.item.json.stdout; + const claudeResponse = JSON.parse(sshOutput); + return { + answer: claudeResponse.result, + cost: claudeResponse.total_cost_usd, + duration_seconds: claudeResponse.duration_ms / 1000, + session_id: claudeResponse.session_id + }; + ``` +- [x] Executed workflow successfully โœ… +- [x] Verified Claude Code response: "2 + 2 = 4" + +#### 6.4.5 Advanced: Session Management Workflow +- [ ] Add **Code** node to generate session UUID: + ```javascript + const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + const r = Math.random() * 16 | 0; + const v = c == 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + return [{ json: { sessionId: uuid } }]; + ``` +- [ ] Add **SSH** node for initial query: + - **Command:** `claude -p "{{ $json.prompt }}" --session-id {{ $json.sessionId }}` +- [ ] Add **SSH** node for follow-up: + - **Command:** `claude -r --session-id {{ $('UUID Generator').item.json.sessionId }} -p "{{ $json.followup }}"` +- [ ] Test multi-turn conversation + +#### 6.4.6 Optional: Slack Integration +- [ ] Install Slack app in n8n +- [ ] Create workflow triggered by Slack messages +- [ ] Use SSH node to send message to Claude Code +- [ ] Return Claude response to Slack thread +- [ ] Implement session tracking for conversations + +#### 6.4.7 Optional: Tool Deployment +For automated skill deployment (UniFi, infrastructure tasks): +- [ ] Update SSH command to include `--dangerously-skip-permissions`: + ```bash + claude --dangerously-skip-permissions -p "Your task requiring tools" + ``` +- [ ] Test with infrastructure directory context: + ```bash + cd /path/to/infrastructure && claude -p "Check WireGuard status" + ``` + +#### 6.4.8 Verification & Testing โœ… BASIC TESTING COMPLETE +- [x] Test basic headless command from n8n โœ… +- [ ] Test session-based multi-turn conversation (optional - future enhancement) +- [x] Verify Claude Code can access local files on HOMELAB-COMMAND โœ… +- [ ] Test error handling (network disconnect, invalid commands) (optional - future enhancement) +- [ ] Monitor resource usage on HOMELAB-COMMAND during heavy Claude operations (ongoing) +- [x] Document SSH requirements: Must use `-n` flag or "Disable Stdin" option in n8n + +#### 6.4.9 Production Considerations +- [ ] Set appropriate SSH timeout in n8n (default may be too short for complex Claude tasks) +- [ ] Configure Claude Code project context on HOMELAB-COMMAND: + - Clone infrastructure repo to known location + - Set up CLAUDE.md in project directory +- [ ] Consider output length limits (Slack: 4000 chars, n8n processing limits) +- [ ] Set up logging for Claude Code executions +- [ ] Add error notifications to n8n workflow +- [ ] Optional: Add Pangolin route for public n8n access (with Authentik SSO) + +### 6.5 RustDesk ID Server (10.0.10.23) +- [ ] Create VM/container on main-pve +- [ ] Assign static IP 10.0.10.23 +- [ ] Install RustDesk hbbs (ID server) +- [ ] Configure relay server on VPS (hbbr) +- [ ] Test RustDesk client connections + +### 6.6 Prometheus + Grafana (10.0.10.25) +- [ ] Create VM/container on main-pve +- [ ] Assign static IP 10.0.10.25 +- [ ] Install Prometheus and Grafana +- [ ] Configure data sources +- [ ] Integrate with Authentik for SSO +- [ ] Set up monitoring targets +- [ ] Add Pangolin route (if public access needed) + +--- + +## Phase 7: Cleanup & Decommission + +**Estimated Time:** 15 minutes +**Risk Level:** Low (removing unused services) + +### 7.1 Remove Spoolman +- [ ] Verify spoolman is not in use +- [ ] Backup any data (if needed): `vzdump CTID --storage backup` +- [ ] Stop VM/container: `pct stop CTID` or `qm stop VMID` +- [ ] Delete VM/container: `pct destroy CTID` or `qm destroy VMID` +- [ ] Remove Pangolin route (already done in Phase 4) +- [ ] Reclaim IP 10.0.10.71 + +### 7.2 Remove Authelia +- [ ] Verify authelia is not in use (replaced by Authentik) +- [ ] Backup configuration (if needed for migration reference) +- [ ] Stop VM/container +- [ ] Delete VM/container +- [ ] Reclaim IP 10.0.10.112 + +--- + +## Phase 8: Update All Documentation โœ… COMPLETED + +**Completion Date:** 2025-12-29 +**Actual Time:** ~1 hour +**Status:** โœ… Documentation synchronized + +- [x] Update infrastructure-audit.md with final IP assignments +- [x] Update CLAUDE.md with correct network (10.0.10.x) - Already up to date +- [x] Update SERVICES.md with new service IPs +- [x] Update RUNBOOK.md if procedures changed - No changes needed +- [x] Update MONITORING.md with new service endpoints - Deferred to monitoring setup +- [x] Git commit all documentation changes +- [x] Git push to sync across machines + +--- + +## Final Verification โœ… COMPLETED + +**Verification Date:** 2025-12-29 + +- [x] All critical services accessible via local IP + - โœ… Proxmox main-pve (10.0.10.3:8006) + - โœ… PostgreSQL (10.0.10.20) + - โœ… Authentik SSO (10.0.10.21:9000) + - โœ… n8n (10.0.10.22:5678) + - โœ… Home Assistant (10.0.10.24:8123) + - โœ… Dockge (10.0.10.27:5001) + - โœ… 3D Printer (10.0.10.30) +- [x] All public services accessible via nianticbooks.com domains + - โœ… freddesk.nianticbooks.com โ†’ Proxmox (working) + - โœ… ad5m.nianticbooks.com โ†’ 3D Printer (working) + - โš ๏ธ bob.nianticbooks.com โ†’ Home Assistant (502 - needs HTTPS backend config in Caddy) +- [x] WireGuard tunnel stable and monitored + - โœ… Tunnel operational (VPS 10.0.8.1 โ†” UCG Ultra 10.0.8.2) + - โœ… Caddy reverse proxy functional + - โœ… Services accessible through tunnel +- [x] No DHCP conflicts in range 10.0.10.50-254 +- [x] All reservations documented in IP-ALLOCATION.md +- [x] Documentation updated and pushed to GitHub + +--- + +## Notes & Issues Encountered + +``` +[Add any notes, problems encountered, or deviations from the plan] + + + + + +``` + +--- + +## Migration Summary + +**Start Date:** 2025-12-11 +**Completion Date:** 2025-12-29 +**Completed By:** Fred (with Claude Code assistance) +**Total Time:** ~2 weeks (cumulative work across multiple sessions) + +### Completed Phases: +- โœ… Phase 1: UCG Ultra DHCP Configuration +- โœ… Phase 2: Update Existing DHCP Reservations +- โœ… Phase 3: Create New Reservations for VMs +- โœ… Phase 4: Update Reverse Proxy Routes (Caddy replaced Pangolin) +- โœ… Phase 5: Configure WireGuard Tunnel +- โœ… Phase 6: Deploy New Services (PostgreSQL, Authentik, n8n, n8n+Claude integration) +- โธ๏ธ Phase 7: Cleanup & Decommission (deferred - non-critical) +- โœ… Phase 8: Update All Documentation + +### Outstanding Items (Non-Critical): +See INFRASTRUCTURE-TODO.md for: +- RustDesk deployment (10.0.10.23) +- Prometheus + Grafana deployment (10.0.10.25) +- Cleanup of deprecated VMs (Spoolman, Authelia) +- Home Assistant Caddy HTTPS backend configuration +- n8n+Claude advanced features (session management, Slack integration) + +### Key Achievements: +- โœ… Network reorganized to 10.0.10.0/24 with clean IP allocation +- โœ… WireGuard tunnel operational (VPS โ†” UCG Ultra) +- โœ… Public services accessible via nianticbooks.com domains +- โœ… Caddy reverse proxy deployed (simpler than Pangolin) +- โœ… Authentik SSO integrated with all Proxmox hosts +- โœ… PostgreSQL shared database serving multiple services +- โœ… n8n workflow automation with Claude Code integration +- โœ… Complete documentation of infrastructure state diff --git a/infrastructure/MONITORING.md b/infrastructure/MONITORING.md new file mode 100644 index 0000000..e205d54 --- /dev/null +++ b/infrastructure/MONITORING.md @@ -0,0 +1,655 @@ +# Monitoring Setup Guide + +This guide provides step-by-step instructions for setting up monitoring for your infrastructure. + +## Table of Contents +- [Monitoring Strategy](#monitoring-strategy) +- [Quick Start: Uptime Kuma](#quick-start-uptime-kuma) +- [Comprehensive Stack: Prometheus + Grafana](#comprehensive-stack-prometheus--grafana) +- [Log Aggregation: Loki](#log-aggregation-loki) +- [Alerting Setup](#alerting-setup) +- [Dashboards](#dashboards) +- [Maintenance](#maintenance) + +--- + +## Monitoring Strategy + +### What to Monitor + +**Infrastructure Level**: +- VPS: CPU, RAM, disk, network +- Proxmox nodes: CPU, RAM, disk, network +- OMV storage: Disk usage, SMART status +- Network: Bandwidth, connectivity + +**Service Level**: +- Service uptime and response time +- HTTP/HTTPS endpoints +- Gerbil tunnel status +- SSL certificate expiration +- Backup job success/failure + +**Application Level**: +- Application-specific metrics +- Error rates +- Request rates +- Database performance + +### Monitoring Tiers + +| Tier | Solution | Complexity | Setup Time | Cost | +|------|----------|------------|------------|------| +| Basic | Uptime Kuma | Low | 30 min | Free | +| Intermediate | Prometheus + Grafana | Medium | 2-4 hours | Free | +| Advanced | Full observability stack | High | 8+ hours | Free/Paid | + +--- + +## Quick Start: Uptime Kuma + +**Best for**: Simple uptime monitoring with alerts + +### Installation + +```bash +# On a Proxmox container or VM +# Create container with Ubuntu/Debian + +# Install Docker +curl -fsSL https://get.docker.com | sh + +# Run Uptime Kuma +docker run -d --restart=always \ + -p 3001:3001 \ + -v uptime-kuma:/app/data \ + --name uptime-kuma \ + louislam/uptime-kuma:1 + +# Access at http://HOST_IP:3001 +``` + +### Configuration + +1. **Create Admin Account** + - First login creates admin user + +2. **Add Monitors** + + **HTTP(S) Monitor**: + - Monitor Type: HTTP(s) + - Friendly Name: Service Name + - URL: https://service.example.com + - Heartbeat Interval: 60 seconds + - Retries: 3 + + **Ping Monitor**: + - Monitor Type: Ping + - Hostname: VPS or node IP + - Interval: 60 seconds + + **Port Monitor**: + - Monitor Type: Port + - Hostname: IP address + - Port: Service port + +3. **Set Up Notifications** + - Settings โ†’ Notifications + - Add notification method (email, Slack, Discord, etc.) + - Test notification + +4. **Create Status Page** (Optional) + - Status Pages โ†’ Add Status Page + - Add monitors to display + - Make public or private + +### Monitors to Create + +- [ ] VPS SSH (Port 22 or custom) +- [ ] VPS HTTP/HTTPS (Port 80/443) +- [ ] Each public service endpoint +- [ ] Proxmox web interface (each node) +- [ ] OMV web interface + +--- + +## Comprehensive Stack: Prometheus + Grafana + +**Best for**: Detailed metrics, trending, and advanced alerting + +### Architecture + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Exporters โ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚ Prometheusโ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚ Grafana โ”‚ +โ”‚ (Metrics) โ”‚ โ”‚ (Storage) โ”‚ โ”‚ (UI) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚Alertmanager โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### Installation (Docker Compose) + +```bash +# Create monitoring directory +mkdir -p ~/monitoring +cd ~/monitoring + +# Create docker-compose.yml +cat > docker-compose.yml <<'EOF' +version: '3.8' + +services: + prometheus: + image: prom/prometheus:latest + container_name: prometheus + restart: unless-stopped + ports: + - "9090:9090" + volumes: + - ./prometheus.yml:/etc/prometheus/prometheus.yml + - prometheus-data:/prometheus + command: + - '--config.file=/etc/prometheus/prometheus.yml' + - '--storage.tsdb.retention.time=30d' + + grafana: + image: grafana/grafana:latest + container_name: grafana + restart: unless-stopped + ports: + - "3000:3000" + volumes: + - grafana-data:/var/lib/grafana + environment: + - GF_SECURITY_ADMIN_PASSWORD=changeme + - GF_INSTALL_PLUGINS=grafana-piechart-panel + + node-exporter: + image: prom/node-exporter:latest + container_name: node-exporter + restart: unless-stopped + ports: + - "9100:9100" + command: + - '--path.rootfs=/host' + volumes: + - '/:/host:ro,rslave' + +volumes: + prometheus-data: + grafana-data: +EOF + +# Create Prometheus config +cat > prometheus.yml <<'EOF' +global: + scrape_interval: 15s + evaluation_interval: 15s + +scrape_configs: + # Prometheus itself + - job_name: 'prometheus' + static_configs: + - targets: ['localhost:9090'] + + # Node exporter (system metrics) + - job_name: 'node' + static_configs: + - targets: ['node-exporter:9100'] + labels: + instance: 'monitoring-host' + + # Add more exporters here + # - job_name: 'proxmox' + # static_configs: + # - targets: ['proxmox-node-1:9221'] +EOF + +# Start services +docker-compose up -d +``` + +### Access + +- **Prometheus**: http://HOST_IP:9090 +- **Grafana**: http://HOST_IP:3000 (admin/changeme) + +### Configure Grafana + +1. **Add Prometheus Data Source** + - Configuration โ†’ Data Sources โ†’ Add data source + - Select Prometheus + - URL: http://prometheus:9090 + - Click "Save & Test" + +2. **Import Dashboards** + - Dashboard โ†’ Import + - Import these popular dashboards: + - 1860: Node Exporter Full + - 10180: Proxmox via Prometheus + - 763: Disk I/O performance + +### Install Exporters + +**Node Exporter** (on each host to monitor): +```bash +# Download +wget https://github.com/prometheus/node_exporter/releases/download/v1.7.0/node_exporter-1.7.0.linux-amd64.tar.gz +tar xvfz node_exporter-1.7.0.linux-amd64.tar.gz +sudo cp node_exporter-1.7.0.linux-amd64/node_exporter /usr/local/bin/ + +# Create systemd service +sudo cat > /etc/systemd/system/node_exporter.service <<'EOF' +[Unit] +Description=Node Exporter +After=network.target + +[Service] +Type=simple +ExecStart=/usr/local/bin/node_exporter + +[Install] +WantedBy=multi-user.target +EOF + +sudo systemctl daemon-reload +sudo systemctl enable node_exporter +sudo systemctl start node_exporter + +# Verify +curl http://localhost:9100/metrics +``` + +**Proxmox VE Exporter**: +```bash +# On Proxmox node +wget https://github.com/prometheus-pve/prometheus-pve-exporter/releases/latest/download/pve_exporter +chmod +x pve_exporter +sudo mv pve_exporter /usr/local/bin/ + +# Create config +sudo mkdir -p /etc/prometheus +sudo cat > /etc/prometheus/pve.yml < /etc/systemd/system/pve_exporter.service <<'EOF' +[Unit] +Description=Proxmox VE Exporter +After=network.target + +[Service] +Type=simple +ExecStart=/usr/local/bin/pve_exporter /etc/prometheus/pve.yml + +[Install] +WantedBy=multi-user.target +EOF + +sudo systemctl daemon-reload +sudo systemctl enable pve_exporter +sudo systemctl start pve_exporter +``` + +**Blackbox Exporter** (for HTTP/HTTPS probing): +```bash +# Add to docker-compose.yml + blackbox: + image: prom/blackbox-exporter:latest + container_name: blackbox-exporter + restart: unless-stopped + ports: + - "9115:9115" + volumes: + - ./blackbox.yml:/etc/blackbox_exporter/config.yml +``` + +```yaml +# blackbox.yml +modules: + http_2xx: + prober: http + timeout: 5s + http: + valid_http_versions: ["HTTP/1.1", "HTTP/2.0"] + valid_status_codes: [] + method: GET + tcp_connect: + prober: tcp + timeout: 5s +``` + +### Add Scrape Targets + +Add to `prometheus.yml`: +```yaml + # VPS Node Exporter + - job_name: 'vps' + static_configs: + - targets: ['VPS_IP:9100'] + + # Proxmox Nodes + - job_name: 'proxmox' + static_configs: + - targets: ['PROXMOX_IP_1:9221', 'PROXMOX_IP_2:9221'] + + # HTTP Endpoints + - job_name: 'blackbox' + metrics_path: /probe + params: + module: [http_2xx] + static_configs: + - targets: + - https://service1.example.com + - https://service2.example.com + relabel_configs: + - source_labels: [__address__] + target_label: __param_target + - source_labels: [__param_target] + target_label: instance + - target_label: __address__ + replacement: blackbox:9115 +``` + +--- + +## Log Aggregation: Loki + +**Best for**: Centralized logging from all services + +### Installation + +Add to `docker-compose.yml`: +```yaml + loki: + image: grafana/loki:latest + container_name: loki + restart: unless-stopped + ports: + - "3100:3100" + volumes: + - ./loki-config.yml:/etc/loki/local-config.yaml + - loki-data:/loki + + promtail: + image: grafana/promtail:latest + container_name: promtail + restart: unless-stopped + volumes: + - ./promtail-config.yml:/etc/promtail/config.yml + - /var/log:/var/log + command: -config.file=/etc/promtail/config.yml + +volumes: + loki-data: +``` + +### Configure Loki in Grafana + +1. Configuration โ†’ Data Sources โ†’ Add data source +2. Select Loki +3. URL: http://loki:3100 +4. Save & Test + +### Query Logs + +In Grafana Explore: +```logql +# All logs +{job="varlogs"} + +# Filter by service +{job="varlogs"} |= "pangolin" + +# Error logs +{job="varlogs"} |= "error" +``` + +--- + +## Alerting Setup + +### Prometheus Alerting Rules + +Create `alerts.yml`: +```yaml +groups: + - name: infrastructure + interval: 30s + rules: + # Node down + - alert: InstanceDown + expr: up == 0 + for: 5m + labels: + severity: critical + annotations: + summary: "Instance {{ $labels.instance }} down" + description: "{{ $labels.instance }} has been down for more than 5 minutes" + + # High CPU + - alert: HighCPU + expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80 + for: 10m + labels: + severity: warning + annotations: + summary: "High CPU on {{ $labels.instance }}" + description: "CPU usage is above 80% for 10 minutes" + + # High Memory + - alert: HighMemory + expr: (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 < 10 + for: 5m + labels: + severity: warning + annotations: + summary: "Low memory on {{ $labels.instance }}" + description: "Available memory is below 10%" + + # Disk Space + - alert: LowDiskSpace + expr: (node_filesystem_avail_bytes / node_filesystem_size_bytes) * 100 < 10 + for: 5m + labels: + severity: critical + annotations: + summary: "Low disk space on {{ $labels.instance }}" + description: "Disk space is below 10% on {{ $labels.mountpoint }}" + + # SSL Certificate Expiring + - alert: SSLCertExpiringSoon + expr: probe_ssl_earliest_cert_expiry - time() < 86400 * 30 + for: 1h + labels: + severity: warning + annotations: + summary: "SSL certificate expiring soon" + description: "Certificate for {{ $labels.instance }} expires in less than 30 days" +``` + +### Alertmanager Configuration + +```yaml +# alertmanager.yml +global: + resolve_timeout: 5m + +route: + group_by: ['alertname', 'cluster'] + group_wait: 10s + group_interval: 10s + repeat_interval: 12h + receiver: 'default' + +receivers: + - name: 'default' + email_configs: + - to: 'your-email@example.com' + from: 'alertmanager@example.com' + smarthost: 'smtp.example.com:587' + auth_username: 'username' + auth_password: 'password' + + # Slack + - name: 'slack' + slack_configs: + - api_url: 'https://hooks.slack.com/services/YOUR/WEBHOOK/URL' + channel: '#alerts' + text: '{{ range .Alerts }}{{ .Annotations.description }}{{ end }}' +``` + +--- + +## Dashboards + +### Essential Dashboards + +1. **Infrastructure Overview** + - All nodes status + - Overall resource utilization + - Service uptime + +2. **VPS Dashboard** + - CPU, RAM, disk, network + - Running services + - Firewall connections + +3. **Proxmox Cluster** + - Cluster health + - VM/container count and status + - Resource allocation vs usage + +4. **Storage** + - Disk space trends + - I/O performance + - SMART status + +5. **Services** + - Uptime percentage + - Response times + - Error rates + +6. **Tunnels** + - Gerbil tunnel status + - Connection count + - Bandwidth usage + +### Creating Custom Dashboard + +1. Grafana โ†’ Create โ†’ Dashboard +2. Add Panel โ†’ Select visualization +3. Write PromQL query +4. Configure thresholds and alerts +5. Save dashboard + +--- + +## Maintenance + +### Regular Tasks + +**Daily**: +- Review alerts +- Check dashboard for anomalies + +**Weekly**: +- Review resource trends +- Check for unused monitors +- Update dashboards + +**Monthly**: +- Review and tune alert thresholds +- Clean up old metrics +- Update monitoring stack +- Test alerting + +**Quarterly**: +- Review monitoring strategy +- Evaluate new monitoring tools +- Update documentation + +### Troubleshooting + +**Prometheus not scraping**: +```bash +# Check targets +curl http://localhost:9090/targets + +# Check Prometheus logs +docker logs prometheus +``` + +**Grafana dashboard empty**: +- Verify data source connection +- Check time range +- Verify metrics exist in Prometheus + +**No alerts firing**: +- Check alerting rules syntax +- Verify Alertmanager connection +- Test alert evaluation + +--- + +## Monitoring Checklist + +### Initial Setup +- [ ] Choose monitoring tier (Basic/Intermediate/Advanced) +- [ ] Deploy monitoring stack +- [ ] Install exporters on all hosts +- [ ] Configure Grafana data sources +- [ ] Import/create dashboards +- [ ] Set up alerting +- [ ] Configure notification channels +- [ ] Test alerts + +### Monitors to Configure +- [ ] VPS uptime and resources +- [ ] Proxmox node resources +- [ ] OMV storage capacity +- [ ] All public HTTP(S) endpoints +- [ ] Gerbil tunnel status +- [ ] SSL certificate expiration +- [ ] Backup job success +- [ ] Network connectivity +- [ ] Service-specific metrics + +### Alerts to Configure +- [ ] Service down (>5 min) +- [ ] High CPU (>80% for 10 min) +- [ ] High memory (>90% for 5 min) +- [ ] Low disk space (<10%) +- [ ] SSL cert expiring (<30 days) +- [ ] Backup failure +- [ ] Tunnel disconnected + +--- + +## Cost Considerations + +### Free Tier Options +- **Uptime Kuma**: Fully free, self-hosted +- **Prometheus + Grafana**: Free, self-hosted +- **Grafana Cloud**: Free tier available (limited) + +### Paid Options (if needed) +- **Datadog**: $15/host/month +- **New Relic**: $99/month+ +- **Better Uptime**: $10/month+ + +**Recommendation**: Start with free self-hosted tools, upgrade only if needed. + +--- + +**Last Updated**: _____________ +**Next Review**: _____________ +**Version**: 1.0 diff --git a/infrastructure/MORNING-REMINDER.md b/infrastructure/MORNING-REMINDER.md new file mode 100644 index 0000000..056d832 --- /dev/null +++ b/infrastructure/MORNING-REMINDER.md @@ -0,0 +1,70 @@ +# Morning Reminder + +## Questions to Ask Claude Code + +### GitHub Sync for Multiple Claude Code Sessions + +**Question:** How can I use GitHub to synchronize my Claude Code sessions across different machines? + +**Context:** +- I have Claude Code running on multiple machines: + - VPS (ubuntu-24.04 - 66.63.182.168) + - Mac Pro 2013 (Sequoia via Open Core Legacy Patcher) + - Potentially other development machines + +**What I want to understand:** +- How to keep this infrastructure repository in sync across all machines +- Best practices for Git workflow with Claude Code on multiple machines +- How to ensure changes made in one Claude Code session are available in others +- Whether I should use branches for machine-specific work or work directly on main +- How to handle potential merge conflicts when working from different locations +- Whether Claude Code has any built-in features for multi-machine workflows + +**Use Cases:** +1. Start work on VPS, continue on Mac Pro +2. Document infrastructure changes from whichever machine I'm on +3. Run scripts and update documentation in the same repo from different locations +4. Ensure consistency across all documentation and automation scripts + +--- + +## Git Workflow for Multiple Machines (Quick Reference) + +**On laptop (first time):** +```bash +git clone https://github.com/FredN9MRQ/infrastructure.git +cd infrastructure +claude # Start Claude Code (install first if needed) +``` + +**When switching between machines:** + +**Before leaving current machine:** +```bash +git add . # Stage all changes +git commit -m "Description here" # Save snapshot +git push # Upload to GitHub +``` + +**On new machine:** +```bash +cd infrastructure +git pull # Download latest changes +claude # Continue working +``` + +**All your work is now synced!** Any changes on desktop โ†’ laptop โ†’ VPS stay in sync. + +--- + +## Today's Priority Tasks + +1. **Configure WireGuard tunnel between UCG Ultra and VPS** โ† CRITICAL - services are down +2. Continue filling out infrastructure-audit.md (Proxmox, network config) +3. Plan IP addressing scheme and DHCP pool boundaries + +--- + +**Created:** 2025-11-14 (midnight) +**Status:** โœ… All changes committed and pushed to GitHub - ready for laptop! +**Read this first thing in the morning!** diff --git a/infrastructure/MQTT-SETUP.md b/infrastructure/MQTT-SETUP.md new file mode 100644 index 0000000..5eee3a8 --- /dev/null +++ b/infrastructure/MQTT-SETUP.md @@ -0,0 +1,744 @@ +# MQTT Broker Setup Guide (Mosquitto) + +This guide covers deploying a Mosquitto MQTT broker for Home Assistant and other IoT integrations on your Proxmox infrastructure. + +## What is MQTT? + +MQTT (Message Queuing Telemetry Transport) is a lightweight messaging protocol designed for IoT devices. It enables efficient publish/subscribe communication between devices and services like Home Assistant. + +**Common Uses:** +- Smart home device communication (sensors, switches, lights) +- ESP8266/ESP32 devices via ESPHome +- Zigbee2MQTT and Z-Wave integration +- Custom automation scripts +- Inter-service messaging + +## Deployment Overview + +**Service:** Mosquitto MQTT Broker +**Location:** pve-router (i5 Proxmox node at 10.0.10.2) +**IP Address:** 10.0.10.26 +**Deployment Method:** LXC Container (lightweight, efficient) + +## Prerequisites + +- Access to Proxmox web interface (https://10.0.10.2:8006) +- Ubuntu/Debian LXC template downloaded on Proxmox +- DHCP reservation configured on UCG Ultra for 10.0.10.26 + +## Part 1: Create LXC Container + +### 1.1 Download Container Template + +In Proxmox web interface on pve-router: + +1. Select **pve-router** node +2. Click **local (pve-router)** storage +3. Click **CT Templates** +4. Click **Templates** button +5. Search for and download: **ubuntu-22.04-standard** or **debian-12-standard** + +### 1.2 Create Container + +1. Click **Create CT** (top right) +2. Configure as follows: + +**General Tab:** +- Node: `pve-router` +- CT ID: (next available, e.g., 105) +- Hostname: `mosquitto` +- Password: (set a strong root password) +- SSH public key: (optional, recommended) + +**Template Tab:** +- Storage: `local` +- Template: `ubuntu-22.04-standard` (or debian-12) + +**Disks Tab:** +- Storage: `local-lvm` (or your preferred storage) +- Disk size: `2 GiB` (plenty for MQTT logs and config) + +**CPU Tab:** +- Cores: `1` + +**Memory Tab:** +- Memory (MiB): `512` +- Swap (MiB): `512` + +**Network Tab:** +- Bridge: `vmbr0` +- IPv4: `Static` +- IPv4/CIDR: `10.0.10.26/24` +- Gateway: `10.0.10.1` +- IPv6: `SLAAC` (or disable if not using IPv6) + +**DNS Tab:** +- DNS domain: `home` (or leave blank) +- DNS servers: `10.0.10.1` (UCG Ultra) + +3. Click **Finish** (uncheck "Start after created" - we'll configure first) + +### 1.3 Configure UCG Ultra DHCP Reservation (Optional Backup) + +Even though we're using static IP, create a DHCP reservation as backup: + +1. Access UCG Ultra at https://10.0.10.1 +2. Settings โ†’ Networks โ†’ LAN โ†’ DHCP +3. Add reservation: + - **IP Address:** 10.0.10.26 + - **MAC Address:** (get from Proxmox container network tab after first start) + - **Hostname:** mosquitto + - **Description:** MQTT Broker (Mosquitto) + +## Part 2: Install and Configure Mosquitto + +### 2.1 Start Container and Login + +In Proxmox: +1. Select the mosquitto container +2. Click **Start** +3. Click **Console** (or SSH via `ssh root@10.0.10.26`) + +### 2.2 Update System + +```bash +# Update package lists +apt update + +# Upgrade existing packages +apt upgrade -y + +# Install basic utilities +apt install -y curl wget nano htop +``` + +### 2.3 Install Mosquitto + +```bash +# Install Mosquitto broker and clients +apt install -y mosquitto mosquitto-clients + +# Enable and start service +systemctl enable mosquitto +systemctl start mosquitto + +# Verify it's running +systemctl status mosquitto +``` + +### 2.4 Configure Mosquitto + +**Create configuration file:** + +```bash +# Backup original config +cp /etc/mosquitto/mosquitto.conf /etc/mosquitto/mosquitto.conf.bak + +# Create new configuration +nano /etc/mosquitto/mosquitto.conf +``` + +**Basic Configuration:** + +```conf +# /etc/mosquitto/mosquitto.conf +# Basic MQTT broker configuration + +# Listener settings +listener 1883 +protocol mqtt + +# Allow anonymous connections (for initial testing only) +allow_anonymous true + +# Persistence settings +persistence true +persistence_location /var/lib/mosquitto/ + +# Logging +log_dest file /var/log/mosquitto/mosquitto.log +log_dest stdout +log_type error +log_type warning +log_type notice +log_type information + +# Connection, protocol, and logging settings +connection_messages true +log_timestamp true +``` + +**Save and exit** (Ctrl+X, Y, Enter) + +### 2.5 Restart Mosquitto + +```bash +systemctl restart mosquitto +systemctl status mosquitto +``` + +## Part 3: Secure MQTT with Authentication + +### 3.1 Create MQTT User + +```bash +# Create password file for user 'homeassistant' +mosquitto_passwd -c /etc/mosquitto/passwd homeassistant + +# You'll be prompted to enter a password twice +# Save this password - you'll need it for Home Assistant +``` + +**Add additional users:** + +```bash +# Add more users (without -c flag to avoid overwriting) +mosquitto_passwd /etc/mosquitto/passwd esphome +mosquitto_passwd /etc/mosquitto/passwd automation +``` + +### 3.2 Update Configuration for Authentication + +Edit config: + +```bash +nano /etc/mosquitto/mosquitto.conf +``` + +**Change this line:** +```conf +allow_anonymous true +``` + +**To:** +```conf +allow_anonymous false +password_file /etc/mosquitto/passwd +``` + +**Save and restart:** + +```bash +systemctl restart mosquitto +systemctl status mosquitto +``` + +## Part 4: Testing MQTT + +### 4.1 Test Locally on Container + +**Terminal 1 - Subscribe to test topic:** + +```bash +mosquitto_sub -h localhost -t test/topic -u homeassistant -P YOUR_PASSWORD +``` + +**Terminal 2 - Publish message:** + +```bash +mosquitto_pub -h localhost -t test/topic -m "Hello MQTT!" -u homeassistant -P YOUR_PASSWORD +``` + +You should see "Hello MQTT!" appear in Terminal 1. + +### 4.2 Test from Another Machine + +From any computer on your network: + +```bash +# Subscribe +mosquitto_sub -h 10.0.10.26 -t test/topic -u homeassistant -P YOUR_PASSWORD + +# Publish (from another terminal) +mosquitto_pub -h 10.0.10.26 -t test/topic -m "Remote test" -u homeassistant -P YOUR_PASSWORD +``` + +## Part 5: Integrate with Home Assistant + +### 5.1 Add MQTT Integration + +1. Open Home Assistant at http://10.0.10.24:8123 +2. Go to **Settings โ†’ Devices & Services** +3. Click **Add Integration** +4. Search for **MQTT** +5. Configure: + - **Broker:** `10.0.10.26` + - **Port:** `1883` + - **Username:** `homeassistant` + - **Password:** (password you set earlier) + - Leave other settings as default +6. Click **Submit** + +### 5.2 Verify Connection + +In Home Assistant: +1. Go to **Settings โ†’ Devices & Services** +2. Click on **MQTT** +3. You should see "Connected" + +### 5.3 Test MQTT in Home Assistant + +1. Developer Tools โ†’ **MQTT** +2. **Listen to topic:** `homeassistant/#` +3. Click **Start Listening** +4. In **Publish** section: + - **Topic:** `homeassistant/test` + - **Payload:** `{"message": "Hello from HA"}` + - Click **Publish** +5. You should see the message appear in the listen section + +## Part 6: ESPHome Integration + +If you're using ESPHome (10.0.10.28): + +**Add to your ESPHome device YAML:** + +```yaml +mqtt: + broker: 10.0.10.26 + port: 1883 + username: esphome + password: YOUR_ESPHOME_PASSWORD + discovery: true + discovery_prefix: homeassistant +``` + +This enables ESPHome devices to publish data to Home Assistant via MQTT. + +## Part 7: Advanced Configuration (Optional) + +### 7.1 Enable TLS/SSL Encryption + +**Generate self-signed certificate (for internal use):** + +```bash +# Install certbot or use openssl +apt install -y openssl + +# Create directory for certificates +mkdir -p /etc/mosquitto/certs +cd /etc/mosquitto/certs + +# Generate CA key and certificate +openssl req -new -x509 -days 3650 -extensions v3_ca -keyout ca.key -out ca.crt + +# Generate server key and certificate +openssl genrsa -out server.key 2048 +openssl req -new -out server.csr -key server.key +openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 3650 + +# Set permissions +chmod 600 /etc/mosquitto/certs/*.key +chown mosquitto:mosquitto /etc/mosquitto/certs/* +``` + +**Update mosquitto.conf:** + +```conf +# Add TLS listener +listener 8883 +protocol mqtt +cafile /etc/mosquitto/certs/ca.crt +certfile /etc/mosquitto/certs/server.crt +keyfile /etc/mosquitto/certs/server.key +``` + +Restart: `systemctl restart mosquitto` + +### 7.2 WebSocket Support (for Browser Clients) + +**Add to mosquitto.conf:** + +```conf +# WebSocket listener +listener 9001 +protocol websockets +``` + +Useful for web-based MQTT clients or browser automation. + +### 7.3 Access Control Lists (ACLs) + +**Create ACL file:** + +```bash +nano /etc/mosquitto/acl +``` + +**Example ACL:** + +```conf +# homeassistant can access everything +user homeassistant +topic readwrite # + +# esphome can only publish sensor data +user esphome +topic write sensors/# +topic read homeassistant/status + +# automation user for scripts +user automation +topic readwrite automation/# +``` + +**Update mosquitto.conf:** + +```conf +acl_file /etc/mosquitto/acl +``` + +Restart: `systemctl restart mosquitto` + +## Part 8: Monitoring and Maintenance + +### 8.1 Check Logs + +```bash +# Real-time log monitoring +tail -f /var/log/mosquitto/mosquitto.log + +# Check systemd logs +journalctl -u mosquitto -f + +# Last 100 lines +journalctl -u mosquitto -n 100 +``` + +### 8.2 Monitor MQTT Traffic + +```bash +# Subscribe to all topics (careful in production!) +mosquitto_sub -h localhost -t '#' -u homeassistant -P YOUR_PASSWORD -v + +# Monitor specific namespace +mosquitto_sub -h localhost -t 'homeassistant/#' -u homeassistant -P YOUR_PASSWORD -v +``` + +### 8.3 Container Resource Monitoring + +```bash +# Check CPU/memory usage +htop + +# Check disk usage +df -h + +# Check mosquitto process +ps aux | grep mosquitto +``` + +### 8.4 Backup Configuration + +```bash +# Create backup script +nano /root/backup-mqtt.sh +``` + +**Backup script:** + +```bash +#!/bin/bash +# MQTT configuration backup + +BACKUP_DIR="/root/mqtt-backups" +DATE=$(date +%Y%m%d-%H%M%S) + +mkdir -p "$BACKUP_DIR" + +# Backup config, passwords, ACLs +tar -czf "$BACKUP_DIR/mqtt-config-$DATE.tar.gz" \ + /etc/mosquitto/mosquitto.conf \ + /etc/mosquitto/passwd \ + /etc/mosquitto/acl \ + /etc/mosquitto/certs/ 2>/dev/null + +# Keep only last 10 backups +ls -t "$BACKUP_DIR"/*.tar.gz | tail -n +11 | xargs -r rm + +echo "Backup completed: mqtt-config-$DATE.tar.gz" +``` + +Make executable: `chmod +x /root/backup-mqtt.sh` + +**Add to crontab:** + +```bash +crontab -e +``` + +Add: `0 2 * * 0 /root/backup-mqtt.sh` (weekly at 2 AM Sunday) + +## Part 9: Firewall Configuration + +### 9.1 UFW Firewall (Optional but Recommended) + +```bash +# Install UFW +apt install -y ufw + +# Allow SSH (IMPORTANT - don't lock yourself out!) +ufw allow 22/tcp + +# Allow MQTT +ufw allow 1883/tcp comment 'MQTT' + +# If using TLS +ufw allow 8883/tcp comment 'MQTT TLS' + +# If using WebSockets +ufw allow 9001/tcp comment 'MQTT WebSocket' + +# Enable firewall +ufw enable + +# Check status +ufw status +``` + +### 9.2 UCG Ultra Firewall + +The UCG Ultra should allow internal LAN traffic by default. No additional rules needed for 10.0.10.0/24 communication. + +## Part 10: Troubleshooting + +### MQTT Service Won't Start + +```bash +# Check for syntax errors in config +mosquitto -c /etc/mosquitto/mosquitto.conf -v + +# Check permissions +ls -la /etc/mosquitto/ +ls -la /var/lib/mosquitto/ +``` + +### Cannot Connect from Home Assistant + +**Check network connectivity:** + +```bash +# From Home Assistant container/VM +ping 10.0.10.26 + +# Check if port is open +nc -zv 10.0.10.26 1883 +``` + +**Verify Mosquitto is listening:** + +```bash +# On MQTT container +ss -tlnp | grep 1883 +``` + +### Authentication Failures + +```bash +# Check password file exists and has correct permissions +ls -la /etc/mosquitto/passwd + +# Test credentials locally +mosquitto_sub -h localhost -t test -u homeassistant -P YOUR_PASSWORD -d +``` + +**Check logs for specific error:** + +```bash +tail -f /var/log/mosquitto/mosquitto.log +``` + +### High Resource Usage + +MQTT is very lightweight. If seeing high CPU/RAM: + +```bash +# Check for message loops or excessive traffic +mosquitto_sub -h localhost -t '#' -u homeassistant -P YOUR_PASSWORD -v | head -100 + +# Check connection count +ss -tn | grep :1883 | wc -l +``` + +## Part 11: Integration Examples + +### 11.1 Basic Sensor in Home Assistant + +```yaml +# configuration.yaml +mqtt: + sensor: + - name: "Temperature Sensor" + state_topic: "home/sensor/temperature" + unit_of_measurement: "ยฐF" + value_template: "{{ value_json.temperature }}" +``` + +**Publish test data:** + +```bash +mosquitto_pub -h 10.0.10.26 -t home/sensor/temperature -m '{"temperature":72.5}' -u homeassistant -P YOUR_PASSWORD +``` + +### 11.2 MQTT Switch in Home Assistant + +```yaml +# configuration.yaml +mqtt: + switch: + - name: "Test Switch" + state_topic: "home/switch/test" + command_topic: "home/switch/test/set" + payload_on: "ON" + payload_off: "OFF" +``` + +### 11.3 ESP8266/ESP32 Example (Arduino) + +```cpp +#include +#include + +const char* ssid = "YourWiFi"; +const char* password = "YourPassword"; +const char* mqtt_server = "10.0.10.26"; +const char* mqtt_user = "esphome"; +const char* mqtt_password = "YOUR_PASSWORD"; + +WiFiClient espClient; +PubSubClient client(espClient); + +void setup() { + client.setServer(mqtt_server, 1883); +} + +void loop() { + if (!client.connected()) { + client.connect("ESP8266Client", mqtt_user, mqtt_password); + } + client.publish("home/sensor/esp", "Hello from ESP8266"); + delay(5000); +} +``` + +## Part 12: Performance Tuning + +### 12.1 Connection Limits + +Edit `/etc/mosquitto/mosquitto.conf`: + +```conf +# Maximum simultaneous client connections +max_connections 100 + +# Maximum QoS 1 and 2 messages in flight +max_inflight_messages 20 + +# Maximum queued messages +max_queued_messages 1000 +``` + +### 12.2 Memory Optimization + +```conf +# Limit memory usage +memory_limit 256M + +# Message size limit (bytes) +message_size_limit 1024000 +``` + +## Network Diagram + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ UCG Ultra (10.0.10.1) โ”‚ +โ”‚ Gateway / DNS / DHCP โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ โ”‚ โ”‚ โ”‚ +โ”Œโ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Home โ”‚ โ”‚ MQTT Broker โ”‚ โ”‚ ESPHome โ”‚ โ”‚ Proxmox โ”‚ +โ”‚ Assistant โ”‚ โ”‚ 10.0.10.26 โ”‚ โ”‚.28 โ”‚ โ”‚ Nodes โ”‚ +โ”‚ .24 โ”‚ โ”‚ Mosquitto โ”‚ โ”‚ โ”‚ โ”‚ .2, .3, .4 โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ โ”‚ โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + MQTT Protocol (Port 1883) + Topics: homeassistant/*, sensors/* +``` + +## Quick Reference + +### Common Commands + +```bash +# Service management +systemctl status mosquitto +systemctl restart mosquitto +systemctl stop mosquitto +systemctl start mosquitto + +# View logs +journalctl -u mosquitto -f +tail -f /var/log/mosquitto/mosquitto.log + +# User management +mosquitto_passwd /etc/mosquitto/passwd USERNAME + +# Testing +mosquitto_sub -h 10.0.10.26 -t '#' -u USER -P PASS -v +mosquitto_pub -h 10.0.10.26 -t test -m "message" -u USER -P PASS +``` + +### Configuration Files + +- **Main config:** `/etc/mosquitto/mosquitto.conf` +- **Passwords:** `/etc/mosquitto/passwd` +- **ACLs:** `/etc/mosquitto/acl` +- **Logs:** `/var/log/mosquitto/mosquitto.log` +- **Data:** `/var/lib/mosquitto/` + +### Network Details + +- **IP:** 10.0.10.26 +- **Hostname:** mosquitto +- **MQTT Port:** 1883 (standard) +- **MQTT TLS Port:** 8883 (if configured) +- **WebSocket Port:** 9001 (if configured) + +## Related Documentation + +- [IP-ALLOCATION.md](IP-ALLOCATION.md) - Network IP plan (MQTT at .26) +- [SERVICES.md](SERVICES.md) - Service inventory +- [infrastructure-audit.md](infrastructure-audit.md) - Current infrastructure state +- [Home Assistant Documentation](https://www.home-assistant.io/integrations/mqtt/) +- [Mosquitto Documentation](https://mosquitto.org/documentation/) + +## Security Best Practices + +1. **Never use `allow_anonymous true` in production** +2. **Use strong passwords** for MQTT users (16+ characters) +3. **Enable TLS/SSL** if accessing over internet or untrusted networks +4. **Use ACLs** to limit user permissions +5. **Regular backups** of configuration and password files +6. **Monitor logs** for suspicious connection attempts +7. **Keep Mosquitto updated:** `apt update && apt upgrade mosquitto` + +## Future Enhancements + +- [ ] Configure TLS encryption for external access +- [ ] Set up Mosquitto bridge to cloud MQTT broker (if needed) +- [ ] Integrate with Authentik SSO (when deployed at 10.0.10.21) +- [ ] Add to Prometheus/Grafana monitoring (when deployed at 10.0.10.25) +- [ ] Configure message retention policies +- [ ] Set up MQTT-based automation scripts + +--- + +**Last Updated:** 2025-11-18 +**Status:** Ready for deployment +**Priority:** Medium (required for Home Assistant IoT integrations) +**Deployment Location:** pve-router (10.0.10.2) +**Resource Requirements:** 1 CPU core, 512MB RAM, 2GB storage diff --git a/infrastructure/OPENCLAW-AUTOMATION-PROMPTS.md b/infrastructure/OPENCLAW-AUTOMATION-PROMPTS.md new file mode 100644 index 0000000..feb416a --- /dev/null +++ b/infrastructure/OPENCLAW-AUTOMATION-PROMPTS.md @@ -0,0 +1,181 @@ +# OpenClaw Automation Prompts + +**Setup Guide for Fred's OpenClaw Desktop** +**Date:** 2026-01-31 + +--- + +## Overview + +This document contains all 4 automation workflows to configure in your OpenClaw desktop app. + +**Quick Start:** Copy each prompt below and paste it into a new conversation in OpenClaw. The agent will understand the scheduling requirements from the prompt. + +--- + +## 1. Morning Brief โ˜€๏ธ + +**Schedule:** Every morning at 8:00 AM (your local time) + +**Prompt:** +``` +I want you to send me a morning brief every morning at 8am my time. I want this morning brief to include: +- The local weather for the day +- A list of a few trending YouTube videos about my interests +- A list of tasks I need to get done today based on my todo list +- Tasks that you think you can do for me today that will be helpful based on what you know about me +- A list of trending stories based on my interests +- Recommendations you can make for me that will make today super productive +``` + +**What it does:** +- Provides daily weather forecast +- Curates YouTube content based on your interests +- Reviews your todo list and suggests priorities +- Identifies automation opportunities +- Delivers relevant news stories +- Offers productivity recommendations + +--- + +## 2. Proactive Coder ๐ŸŒ™ + +**Schedule:** Every night at 11:00 PM (while you sleep) + +**Prompt:** +``` +I am a 1 man business. I work from the moment I wake up to the moment I go to sleep. I need an employee taking as much off my plate and being as proactive as possible. + +Please take everything you know about me and just do work you think would make my life easier or improve my business and make me money. I want to wake up every morning and be like "wow, you got a lot done while I was sleeping." + +Don't be afraid to monitor my business and build things that would help improve our workflow. Just create PRs for me to review, don't push anything live. I'll test and commit. Every night when I go to bed, build something cool I can test. Schedule time to work every night at 11pm. +``` + +**What it does:** +- Works on improvements while you sleep +- Creates pull requests for code changes (never pushes directly) +- Monitors infrastructure and suggests optimizations +- Builds tools to improve your workflows +- Provides morning summary of overnight work +- Focuses on revenue-generating improvements + +--- + +## 3. Second Brain ๐Ÿง  + +**Type:** One-time project setup (not scheduled) + +**Prompt:** +``` +I want you to build me a 2nd brain. This should be a NextJS app that shows a list of documents you create as we work together in a nice document viewer that feels like a mix of Obsidian and Linear. + +I want you to create a folder where all the documents in that folder are viewable in this 2nd brain. Update your memories/skills so that as we talk every day, you create documents in that 2nd brain that explore some of the more important concepts we discuss. + +You should also create daily journal entries that record from a high level all our daily discussions. +``` + +**What it does:** +- Creates a NextJS document viewer app +- Auto-generates concept exploration documents from conversations +- Creates daily journal entries automatically +- Provides Obsidian/Linear-style interface +- Stores knowledge base in organized folder structure +- Enables search and navigation of your knowledge + +--- + +## 4. Afternoon Research Report ๐Ÿ“š + +**Schedule:** Every afternoon at 2:00 PM + +**Prompt:** +``` +I want a daily research report sent to me every afternoon. Based on what you know about me I want you to research and give me a report about a concept that would improve me, processes that would improve our working relationship, or anything else that would be helpful for me. + +Examples would be: +- Deep dives on concepts I'm interested in like machine learning +- A new workflow we can implement together that will improve our productivity +``` + +**What it does:** +- Delivers daily research on topics of interest +- Provides deep dives on technical concepts +- Suggests workflow improvements +- Researches business optimization strategies +- Includes sources and citations +- Tailored to your current projects and interests + +--- + +## How to Configure + +### Method 1: Desktop App (Recommended) +1. Open OpenClaw desktop app on your iMac +2. Start a new conversation +3. Paste one of the prompts above +4. The agent will understand the scheduling from the prompt +5. Repeat for each automation + +### Method 2: CLI (Advanced) +From the Gateway container: +```bash +ssh root@10.0.10.3 +pct exec 130 -- openclaw cron add "0 8 * * *" "Morning Brief" +pct exec 130 -- openclaw cron add "0 23 * * *" "Proactive Coder" +pct exec 130 -- openclaw cron add "0 14 * * *" "Afternoon Research" +``` + +### Method 3: Web Dashboard +Access at: https://openclaw.nianticbooks.home + +--- + +## Tips for Best Results + +1. **Morning Brief:** + - Share your interests and preferences with the agent + - Connect your todo list (n8n, task manager, etc.) + - Provide weather location preferences + +2. **Proactive Coder:** + - Grant the agent access to your git repositories + - Set up PR notifications + - Review and approve changes each morning + +3. **Second Brain:** + - This is a one-time setup, not a scheduled task + - Once built, it runs automatically during conversations + - Check the document viewer regularly to review captured knowledge + +4. **Afternoon Research:** + - Mention topics you're curious about + - The agent learns your interests over time + - Use research reports as learning materials + +--- + +## Gateway Configuration + +**Gateway URL:** ws://10.0.10.28:18789 +**Dashboard:** https://openclaw.nianticbooks.home +**Container:** CT 130 on main-pve (10.0.10.28) +**Desktop Client:** Fred's iMac (10.0.10.11 Ethernet / 10.0.10.144 Wi-Fi) + +--- + +## Resources + +- **Full Setup Guide:** [OPENCLAW-SETUP.md](OPENCLAW-SETUP.md) +- **Quick Start:** [OPENCLAW-QUICKSTART.md](OPENCLAW-QUICKSTART.md) +- **Service Docs:** [SERVICES.md](SERVICES.md) +- **Network Info:** [IP-ALLOCATION.md](IP-ALLOCATION.md) +- **Official Docs:** https://docs.openclaw.ai +- **GitHub:** https://github.com/openclaw/openclaw +- **YouTube Tutorials:** + - Introduction: https://www.youtube.com/watch?v=tEjg56ZYKJo + - Tutorial with Prompts: https://www.youtube.com/watch?v=b-l9sGh1-UY + +--- + +**Last Updated:** 2026-01-31 +**Status:** Ready to configure diff --git a/infrastructure/OPENCLAW-QUICKSTART.md b/infrastructure/OPENCLAW-QUICKSTART.md new file mode 100644 index 0000000..eabd449 --- /dev/null +++ b/infrastructure/OPENCLAW-QUICKSTART.md @@ -0,0 +1,242 @@ +# OpenClaw Quick Start Guide + +**Container**: CT 130 (10.0.10.28) +**Auth Token**: `0b0259af04929be8424b51b6520b4bb48c70d0f595dde9fb6f4c3d5d6410a9fa` +**Status**: Gateway running and accessible on LAN + +## Gateway Status + +โœ… **Onboarding Complete** +โœ… **Gateway Running** (check with `pct exec 130 -- pgrep -f 'openclaw gateway'`) +โœ… **LAN Accessible** at ws://10.0.10.28:18789 +โœ… **Model Configured**: claude-sonnet-4-5 +โœ… **Hooks Enabled**: boot-md, command-logger, session-memory +โœ… **Commands Enabled**: bash (native commands enabled) +โœ… **User Profile**: Created at `/root/USER.md` with Fred's preferences + +### Current Configuration + +- **Bind Address**: LAN (0.0.0.0:18789) +- **Auth Mode**: Token-based +- **Tailscale**: Disabled (using direct LAN access) +- **Dashboard**: + +### Verify Gateway Status + +```bash +# Check if Gateway is running +pct exec 130 -- pgrep -f "openclaw gateway" + +# Test connectivity from Proxmox host +curl -I http://10.0.10.28:18789 + +# Or from your local machine +curl -I http://10.0.10.28:18789 +``` + +## Manual Start/Stop Commands + +If you need to manually control the Gateway: + +```bash +# Start Gateway in foreground (for testing) +ssh root@10.0.10.3 "pct exec 130 -- openclaw gateway" + +# Start Gateway in background +ssh root@10.0.10.3 "pct exec 130 -- nohup openclaw gateway > /var/log/openclaw-gateway.log 2>&1 &" + +# Stop Gateway +ssh root@10.0.10.3 "pct exec 130 -- pkill -f 'openclaw gateway'" + +# View logs +ssh root@10.0.10.3 "pct exec 130 -- tail -f /var/log/openclaw-gateway.log" +``` + +## Systemd Service (If Installed) + +If the onboarding installed a systemd service: + +```bash +# Check status +ssh root@10.0.10.3 "pct exec 130 -- systemctl status openclaw-gateway" + +# Start/Stop/Restart +ssh root@10.0.10.3 "pct exec 130 -- systemctl start openclaw-gateway" +ssh root@10.0.10.3 "pct exec 130 -- systemctl stop openclaw-gateway" +ssh root@10.0.10.3 "pct exec 130 -- systemctl restart openclaw-gateway" + +# Enable auto-start on boot +ssh root@10.0.10.3 "pct exec 130 -- systemctl enable openclaw-gateway" + +# View logs +ssh root@10.0.10.3 "pct exec 130 -- journalctl -u openclaw-gateway -f" +``` + +## Installing Desktop Client (iMac) + +After Gateway is running: + +1. **Download OpenClaw for macOS**: + - Visit: https://openclaw.bot + - Or: https://github.com/openclaw/openclaw/releases + - Download the `.dmg` installer + +2. **Install**: + - Open the `.dmg` file + - Drag OpenClaw to Applications folder + - Open OpenClaw from Applications + +3. **Configure Connection**: + - Gateway URL: `ws://10.0.10.28:18789` + - Auth Token: `0b0259af04929be8424b51b6520b4bb48c70d0f595dde9fb6f4c3d5d6410a9fa` + - Or use the auto-discovery (should find Gateway on LAN) + +4. **Device Pairing**: + - iMac should auto-pair (local network) + - Check Gateway dashboard to approve pairing if needed + +## Configuring Your 4 Automation Workflows + +Once desktop client is connected, configure these workflows: + +### 1. Morning Brief (8:00 AM) + +Create a new agent or scheduled task with this prompt: + +``` +I want you to send me a morning brief every morning at 8am my time. I want this morning brief to include: +- The local weather for the day +- A list of a few trending YouTube videos about my interests +- A list of tasks I need to get done today based on my todo list +- Tasks that you think you can do for me today that will be helpful based on what you know about me +- A list of trending stories based on my interests +- Recommendations you can make for me that will make today super productive +``` + +**Schedule**: Daily at 8:00 AM (use cron: `0 8 * * *`) + +### 2. Proactive Coder (11:00 PM) + +Create a scheduled coding agent: + +``` +I am a 1 man business. I work from the moment I wake up to the moment I go to sleep. I need an employee taking as much off my plate and being as proactive as possible. + +Please take everything you know about me and just do work you think would make my life easier or improve my business and make me money. I want to wake up every morning and be like "wow, you got a lot done while I was sleeping." + +Don't be afraid to monitor my business and build things that would help improve our workflow. Just create PRs for me to review, don't push anything live. I'll test and commit. Every night when I go to bed, build something cool I can test. Schedule time to work every night at 11pm. +``` + +**Schedule**: Daily at 11:00 PM (use cron: `0 23 * * *`) +**Tools Enabled**: file, bash, git (for creating PRs) + +### 3. Second Brain (NextJS Document Viewer) + +Initial setup prompt: + +``` +I want you to build me a 2nd brain. This should be a NextJS app that shows a list of documents you create as we work together in a nice document viewer that feels like a mix of Obsidian and Linear. + +I want you to create a folder where all the documents in that folder are viewable in this 2nd brain. Update your memories/skills so that as we talk every day, you create documents in that 2nd brain that explore some of the more important concepts we discuss. + +You should also create daily journal entries that record from a high level all our daily discussions. +``` + +**This is a project task, not a scheduled task** +**Agent should**: Create the NextJS app, set up document folder, configure auto-documentation + +### 4. Afternoon Research Report + +Create a scheduled research agent: + +``` +I want a daily research report sent to me every afternoon. Based on what you know about me I want you to research and give me a report about a concept that would improve me, processes that would improve our working relationship, or anything else that would be helpful for me. + +Examples would be: +- Deep dives on concepts I'm interested in like machine learning +- A new workflow we can implement together that will improve our productivity +``` + +**Schedule**: Daily at 2:00 PM (use cron: `0 14 * * *`) +**Tools Enabled**: web search, web fetch + +## Troubleshooting + +### Gateway won't start + +```bash +# Check config +ssh root@10.0.10.3 "pct exec 130 -- openclaw doctor" + +# Fix configuration issues +ssh root@10.0.10.3 "pct exec 130 -- openclaw doctor --fix" + +# Check logs +ssh root@10.0.10.3 "pct exec 130 -- cat /var/log/openclaw-gateway.log" +``` + +### Can't connect from iMac + +```bash +# Verify Gateway is listening on LAN interface +ssh root@10.0.10.3 "pct exec 130 -- netstat -tlnp | grep 18789" + +# Test from Proxmox host +ssh root@10.0.10.3 "curl -I http://10.0.10.28:18789" + +# Check firewall (container shouldn't have firewall by default) +ssh root@10.0.10.3 "pct exec 130 -- iptables -L" +``` + +### Auth token issues + +```bash +# Verify token is set +ssh root@10.0.10.3 "pct exec 130 -- openclaw config get gateway.auth.token" + +# Reset token if needed +ssh root@10.0.10.3 "pct exec 130 -- openclaw config set gateway.auth.token NEW_TOKEN_HERE" +``` + +## Configuration Files + +**Main config**: `/root/.openclaw/openclaw.json` +**Gateway state**: `/root/.openclaw/gateway/` +**Agent configs**: `/root/.openclaw/agents/` +**Credentials**: `/root/.openclaw/credentials/` + +## Backup Important Files + +Regularly backup the OpenClaw state: + +```bash +# From Proxmox host +ssh root@10.0.10.3 "pct exec 130 -- tar -czf /tmp/openclaw-backup.tar.gz /root/.openclaw" +ssh root@10.0.10.3 "pct exec 130 -- mv /tmp/openclaw-backup.tar.gz /mnt/omv-backups/openclaw/" +``` + +## Next Steps After Setup + +1. โœ… Complete onboarding +2. โœ… Verify Gateway is running and accessible +3. Install desktop client on iMac +4. Configure device pairing +5. Set up 4 automation workflows +6. Test voice integration on iMac +7. Configure Home Assistant integration +8. Set up n8n webhook integration +9. Test all scheduled tasks + +## Resources + +- **Full Setup Guide**: [OPENCLAW-SETUP.md](OPENCLAW-SETUP.md) +- **Service Docs**: [SERVICES.md](SERVICES.md) +- **Network Info**: [IP-ALLOCATION.md](IP-ALLOCATION.md) +- **Official Docs**: https://docs.openclaw.ai +- **GitHub**: https://github.com/openclaw/openclaw +- **Security Guide**: https://docs.openclaw.ai/gateway/security + +--- + +**Status**: Gateway running and ready for iMac client connection +**Last Updated**: 2026-01-31 diff --git a/infrastructure/OPENCLAW-SETUP.md b/infrastructure/OPENCLAW-SETUP.md new file mode 100644 index 0000000..52e8dc3 --- /dev/null +++ b/infrastructure/OPENCLAW-SETUP.md @@ -0,0 +1,329 @@ +# OpenClaw AI Agent Setup + +**Last Updated:** 2026-01-30 +**Status:** Planned + +## Overview + +OpenClaw is an open-source AI agent coordination platform that enables multi-agent AI workflows with voice integration, smart home control, and cross-platform messaging. + +**Architecture:** +- **Gateway (Control Plane)**: LXC container on main-pve (CT 130, 10.0.10.28) +- **Desktop Client**: macOS app on Fred's iMac (10.0.10.11) +- **Communication**: WebSocket on port 18789 + +## Infrastructure Details + +### Gateway Server (LXC Container) + +**Container Specifications:** +- **Host**: main-pve (10.0.10.3) +- **Container ID**: CT 130 +- **IP Address**: 10.0.10.28 +- **Hostname**: openclaw +- **Resources**: 2 vCPUs, 4GB RAM, 16GB storage +- **OS**: Debian 12 (Bookworm) +- **Port**: 18789 (WebSocket/HTTP) + +**Service Details:** +- **Service Name**: `openclaw-gateway` (systemd) +- **Configuration**: `~/.openclaw/` directory +- **State Storage**: `~/.openclaw/` (backed up to OMV) +- **Node Version**: โ‰ฅ22.12.0 LTS +- **Package Manager**: pnpm 10.23.0+ + +### Desktop Client (iMac) + +**Hardware:** +- **Device**: Late 2013 iMac +- **CPU**: 3.2GHz Quad Core Intel i5 +- **GPU**: Nvidia GeForce GT 755M (1GB) +- **RAM**: 24GB +- **OS**: macOS Sequoia (via OpenCore) +- **IP Address**: 10.0.10.11 (static on device) +- **Hostname**: freds-imac + +**Client Features:** +- Voice input/output for AI interactions +- Morning briefing automation +- macOS system integration +- Desktop notifications + +## Installation Plan + +### Phase 1: Gateway Deployment + +1. **Create LXC Container** + ```bash + ssh root@10.0.10.3 + pct create 130 local:vztmpl/debian-12-standard_12.2-1_amd64.tar.zst \ + --hostname openclaw \ + --memory 4096 \ + --cores 2 \ + --rootfs local:16 \ + --net0 name=eth0,bridge=vmbr0,ip=10.0.10.28/24,gw=10.0.10.1 \ + --nameserver 10.0.10.1 \ + --features nesting=1 \ + --unprivileged 1 \ + --start 1 + ``` + +2. **Install OpenClaw Gateway** + ```bash + pct exec 130 -- bash -c "curl -fsSL https://openclaw.bot/install.sh | bash" + pct exec 130 -- openclaw onboard --install-daemon + ``` + +3. **Configure Network Access** + - Default: Loopback only (`127.0.0.1:18789`) + - LAN Access: Bind to `10.0.10.28:18789` with authentication + - Security: Token-based auth for non-loopback access + +4. **Verify Installation** + ```bash + pct exec 130 -- openclaw doctor + pct exec 130 -- openclaw status + pct exec 130 -- systemctl status openclaw-gateway + ``` + +### Phase 2: Desktop Client Setup + +1. **Install OpenClaw Desktop App on iMac** + - Download from openclaw.bot or GitHub releases + - Install to Applications folder + - Configure Gateway connection: `10.0.10.28:18789` + +2. **Device Pairing** + - iMac connects to Gateway + - Local network: Auto-approval expected + - Verify pairing in Gateway dashboard + +3. **Voice Integration** + - Configure microphone input + - Test voice commands + - Set up wake word (if supported) + +### Phase 3: Agent Configuration & Automation + +**YouTube Tutorial Resources:** +- Introduction Video: https://www.youtube.com/watch?v=tEjg56ZYKJo +- Tutorial with Prompts: https://www.youtube.com/watch?v=b-l9sGh1-UY + +#### 1. Morning Brief (8:00 AM Daily) + +**Schedule:** Every morning at 8:00 AM (Fred's local time) + +**Prompt Template:** +``` +I want you to send me a morning brief every morning at 8am my time. I want this morning brief to include: +- The local weather for the day +- A list of a few trending YouTube videos about my interests +- A list of tasks I need to get done today based on my todo list +- Tasks that you think you can do for me today that will be helpful based on what you know about me +- A list of trending stories based on my interests +- Recommendations you can make for me that will make today super productive +``` + +**Implementation:** +- Configure as recurring agent task or cron job in OpenClaw +- Pull weather from Weather API or Home Assistant integration +- YouTube trending: Use YouTube Data API filtered by interests +- Todo list: Integrate with n8n or local todo system +- News: RSS feeds or news API filtered by interests + +#### 2. Proactive Coder (11:00 PM Nightly) + +**Schedule:** Every night at 11:00 PM (while Fred is sleeping) + +**Prompt Template:** +``` +I am a 1 man business. I work from the moment I wake up to the moment I go to sleep. I need an employee taking as much off my plate and being as proactive as possible. + +Please take everything you know about me and just do work you think would make my life easier or improve my business and make me money. I want to wake up every morning and be like "wow, you got a lot done while I was sleeping." + +Don't be afraid to monitor my business and build things that would help improve our workflow. Just create PRs for me to review, don't push anything live. I'll test and commit. Every night when I go to bed, build something cool I can test. Schedule time to work every night at 11pm. +``` + +**Implementation:** +- Scheduled agent session starting at 11:00 PM +- Agent has access to: + - Code repositories (via SSH or git) + - Infrastructure monitoring data + - Business metrics and analytics +- Output: Pull requests for review (not direct commits) +- Morning summary of work completed overnight + +#### 3. Second Brain (NextJS Document Viewer) + +**Purpose:** Obsidian/Linear-style document viewer for daily knowledge capture + +**Prompt Template:** +``` +I want you to build me a 2nd brain. This should be a NextJS app that shows a list of documents you create as we work together in a nice document viewer that feels like a mix of Obsidian and Linear. + +I want you to create a folder where all the documents in that folder are viewable in this 2nd brain. Update your memories/skills so that as we talk every day, you create documents in that 2nd brain that explore some of the more important concepts we discuss. + +You should also create daily journal entries that record from a high level all our daily discussions. +``` + +**Technical Implementation:** +- NextJS app deployed as container or static site +- Document storage: Local filesystem or database +- Features: + - Markdown document viewer + - Daily journal entries (auto-created) + - Concept exploration documents (auto-created during conversations) + - Search and navigation + - Obsidian/Linear-inspired UI +- Integration: OpenClaw agent writes to document folder automatically + +#### 4. Afternoon Research Report (Daily) + +**Schedule:** Every afternoon (time TBD, suggest 2:00 PM) + +**Prompt Template:** +``` +I want a daily research report sent to me every afternoon. Based on what you know about me I want you to research and give me a report about a concept that would improve me, processes that would improve our working relationship, or anything else that would be helpful for me. + +Examples would be: +- Deep dives on concepts I'm interested in like machine learning +- A new workflow we can implement together that will improve our productivity +``` + +**Implementation:** +- Scheduled agent research session +- Topics determined by: + - Recent conversations and interests + - Current projects and challenges + - Learning goals + - Business improvement opportunities +- Output: Well-researched report with sources +- Delivery: Morning briefing channel or dedicated report folder + +## Integration Points + +### Planned Integrations + +1. **Home Assistant** (10.0.10.24) + - Custom plugin for device control + - Voice commands for smart home + - Morning briefing with home status + +2. **n8n Workflows** (10.0.10.22) + - Webhook triggers from OpenClaw + - Automated task execution + - Custom workflow integration + +3. **Calendar Integration** + - Morning briefing with daily schedule + - Meeting reminders + - Task coordination + +4. **Weather Integration** + - Morning weather briefing + - Daily forecast + +## Security Configuration + +**Gateway Authentication:** +- Token-based auth for LAN access +- No public internet exposure (internal only) +- Optional: Reverse proxy via Caddy Internal (10.0.10.27) + +**Network Access:** +- Loopback: `127.0.0.1:18789` (no auth required) +- LAN Binding: `10.0.10.28:18789` (auth required) +- Firewall: Restrict to 10.0.10.0/24 network + +**Device Pairing:** +- Local clients: Auto-approve +- Remote clients: Manual approval with 1-hour expiration codes + +## Startup & Management + +**Gateway Service:** +```bash +# Check status +pct exec 130 -- systemctl status openclaw-gateway + +# Start/stop/restart +pct exec 130 -- systemctl start openclaw-gateway +pct exec 130 -- systemctl stop openclaw-gateway +pct exec 130 -- systemctl restart openclaw-gateway + +# View logs +pct exec 130 -- journalctl -u openclaw-gateway -f + +# Dashboard +curl http://10.0.10.28:18789/ +``` + +**Desktop Client:** +- Launch OpenClaw app from macOS Applications +- Check connection status in app +- Configure preferences and voice settings + +## Health Checks + +```bash +# Gateway accessibility +curl -I http://10.0.10.28:18789 + +# WebSocket test +wscat -c ws://10.0.10.28:18789 + +# Service status +ssh root@10.0.10.3 'pct exec 130 -- openclaw status' + +# Check Gateway sessions +ssh root@10.0.10.3 'pct exec 130 -- openclaw sessions' +``` + +## Backup Strategy + +**Gateway State Backup:** +- Location: `/root/.openclaw/` on CT 130 +- Backup to: `/mnt/omv-backups/openclaw/` +- Frequency: Daily +- Retention: 7 days + +**Configuration Files:** +- OpenClaw config: `~/.openclaw/` +- Agent profiles: `~/.openclaw/agents/` +- Gateway state: `~/.openclaw/gateway/` + +## Resources + +- **GitHub Repository**: https://github.com/openclaw/openclaw +- **Documentation**: https://docs.openclaw.ai +- **Installation Guide**: https://docs.openclaw.ai/start +- **Gateway Docs**: https://docs.openclaw.ai/gateway +- **Security Guide**: https://docs.openclaw.ai/security +- **Plugin System**: https://docs.openclaw.ai/plugins + +## YouTube Tutorials + +- **Introduction**: https://www.youtube.com/watch?v=tEjg56ZYKJo +- **Tutorial & Prompts**: https://www.youtube.com/watch?v=b-l9sGh1-UY + +## Notes + +- **Node.js Version**: Must use โ‰ฅ22.12.0 for critical security patches (CVE-2025-59466, CVE-2026-21636) +- **Resource Requirements**: Lightweight - 2 vCPUs and 4GB RAM sufficient +- **Multi-Instance**: Can run multiple Gateways on different ports if needed +- **Docker Alternative**: Could use Docker Compose instead of native install if preferred + +## Next Steps + +1. Review YouTube tutorial prompts manually (WebFetch unable to access YouTube) +2. Extract morning briefing templates +3. Create LXC container CT 130 +4. Install OpenClaw Gateway +5. Install desktop client on iMac +6. Configure voice integration +7. Set up morning briefing automation +8. Test Home Assistant integration + +--- + +**Status**: Documentation complete, awaiting deployment diff --git a/infrastructure/PROXMOX-RECOVERY-GUIDE.md b/infrastructure/PROXMOX-RECOVERY-GUIDE.md new file mode 100644 index 0000000..f61017b --- /dev/null +++ b/infrastructure/PROXMOX-RECOVERY-GUIDE.md @@ -0,0 +1,728 @@ +# Proxmox Recovery Guide + +Detailed procedures for recovering Proxmox VE installations, VMs, and containers from various failure scenarios. + +## Table of Contents +- [Overview](#overview) +- [Backup Strategy](#backup-strategy) +- [Recovery Scenarios](#recovery-scenarios) +- [Tools and Commands](#tools-and-commands) +- [Preventive Measures](#preventive-measures) + +## Overview + +This guide covers recovery procedures for Proxmox VE environments, specifically: +- Proxmox node failures (hardware issues, corruption, etc.) +- VM/Container restoration +- Cluster recovery +- Configuration restoration + +## Backup Strategy + +### What to Backup + +**1. Proxmox Configuration** +```bash +# Backup Proxmox configs +tar -czf /backup/proxmox-etc-$(date +%Y%m%d).tar.gz /etc/pve/ + +# Backup network configuration +cp /etc/network/interfaces /backup/interfaces.$(date +%Y%m%d) + +# Backup storage configuration +pvesm status > /backup/storage-status.$(date +%Y%m%d).txt +``` + +**2. VM/Container Backups** +```bash +# Backup all VMs/containers +vzdump --all --mode snapshot --compress zstd --storage [backup-storage] + +# Backup specific VM +vzdump VMID --mode snapshot --compress zstd --storage [backup-storage] + +# Backup to network location +vzdump VMID --dumpdir /mnt/backup --mode snapshot --compress zstd +``` + +**3. Boot Configuration** +```bash +# Backup boot loader +dd if=/dev/sda of=/backup/mbr-backup.img bs=512 count=1 + +# Backup partition table +sfdisk -d /dev/sda > /backup/partition-table.$(date +%Y%m%d).txt +``` + +### Automated Backup Script + +Create `/usr/local/bin/backup-proxmox.sh`: + +```bash +#!/bin/bash +# Automated Proxmox backup script + +BACKUP_DIR="/mnt/backup/proxmox" +DATE=$(date +%Y%m%d-%H%M%S) +RETENTION_DAYS=30 + +# Create backup directory +mkdir -p "$BACKUP_DIR/$DATE" + +# Backup Proxmox configuration +tar -czf "$BACKUP_DIR/$DATE/pve-config.tar.gz" /etc/pve/ 2>/dev/null + +# Backup network config +cp /etc/network/interfaces "$BACKUP_DIR/$DATE/interfaces" + +# Backup storage config +pvesm status > "$BACKUP_DIR/$DATE/storage-status.txt" + +# Backup firewall rules +iptables-save > "$BACKUP_DIR/$DATE/iptables-rules" + +# List all VMs and containers +qm list > "$BACKUP_DIR/$DATE/vm-list.txt" +pct list > "$BACKUP_DIR/$DATE/ct-list.txt" + +# Backup VM configs (excluding disks) +for vm in $(qm list | awk '{if(NR>1) print $1}'); do + qm config $vm > "$BACKUP_DIR/$DATE/vm-$vm-config.txt" +done + +# Backup container configs +for ct in $(pct list | awk '{if(NR>1) print $1}'); do + pct config $ct > "$BACKUP_DIR/$DATE/ct-$ct-config.txt" +done + +# Remove old backups +find "$BACKUP_DIR" -type d -mtime +$RETENTION_DAYS -exec rm -rf {} + + +echo "Backup completed: $BACKUP_DIR/$DATE" +``` + +Set up cron job: +```bash +# Daily backup at 2 AM +0 2 * * * /usr/local/bin/backup-proxmox.sh +``` + +## Recovery Scenarios + +### Scenario 1: Single VM/Container Recovery + +**Symptoms:** +- VM won't start +- VM corrupted +- Accidental deletion + +**Recovery Procedure:** + +**1. From Proxmox Backup** +```bash +# List available backups +ls -lh /var/lib/vz/dump/ + +# Restore VM from backup +qmrestore /var/lib/vz/dump/vzdump-qemu-VMID-DATE.vma.zst NEW_VMID \ + --storage local-lvm + +# Restore container from backup +pct restore NEW_CTID /var/lib/vz/dump/vzdump-lxc-CTID-DATE.tar.zst \ + --storage local-lvm + +# Start restored VM/container +qm start NEW_VMID +pct start NEW_CTID +``` + +**2. From External Backup Location** +```bash +# Mount backup location if needed +mount /dev/sdX1 /mnt/backup + +# Or mount network share +mount -t nfs backup-server:/backups /mnt/backup + +# Restore from external location +qmrestore /mnt/backup/vzdump-qemu-VMID.vma.zst NEW_VMID \ + --storage local-lvm +``` + +**3. Restore to Different Storage** +```bash +# List available storage +pvesm status + +# Restore to specific storage +qmrestore /path/to/backup.vma.zst NEW_VMID --storage [storage-name] +``` + +### Scenario 2: Proxmox Node Complete Failure + +**Symptoms:** +- Hardware failure (motherboard, CPU, RAM) +- Disk controller failure +- Proxmox installation corrupted + +**Recovery Options:** + +**Option A: Reinstall Proxmox and Restore VMs** + +**1. Reinstall Proxmox VE** +```bash +# Boot from Proxmox ISO +# Follow installation wizard +# Configure same network settings as before +# Configure same hostname + +# After installation, update system +apt update && apt full-upgrade +``` + +**2. Restore Network Configuration** +```bash +# Copy backed up network config +scp backup-server:/backup/interfaces /etc/network/interfaces + +# Restart networking +systemctl restart networking +``` + +**3. Configure Storage** +```bash +# Recreate storage configurations +# Web UI: Datacenter โ†’ Storage โ†’ Add + +# Or via command line +pvesm add dir backup --path /mnt/backup --content backup +pvesm add nfs shared-storage --server NFS_IP --export /export/path --content images,backup +``` + +**4. Restore VMs/Containers** +```bash +# Copy backups if needed +scp -r backup-server:/backups/* /var/lib/vz/dump/ + +# Restore each VM +for backup in /var/lib/vz/dump/vzdump-qemu-*.vma.zst; do + VMID=$(basename $backup | grep -oP '\d+') + echo "Restoring VM $VMID..." + qmrestore $backup $VMID --storage local-lvm +done + +# Restore each container +for backup in /var/lib/vz/dump/vzdump-lxc-*.tar.zst; do + CTID=$(basename $backup | grep -oP '\d+') + echo "Restoring CT $CTID..." + pct restore $CTID $backup --storage local-lvm +done +``` + +**Option B: Disk Recovery (If disks are intact)** + +**1. Boot from Proxmox Live ISO** +```bash +# Don't install - boot to rescue mode +``` + +**2. Mount Proxmox System Disk** +```bash +# Identify system disk +lsblk + +# Mount root filesystem +mkdir /mnt/pve-root +mount /dev/sdX3 /mnt/pve-root # Adjust partition number + +# Mount boot partition +mount /dev/sdX2 /mnt/pve-root/boot/efi +``` + +**3. Chroot into System** +```bash +# Mount proc, sys, dev +mount -t proc proc /mnt/pve-root/proc +mount -t sysfs sys /mnt/pve-root/sys +mount -o bind /dev /mnt/pve-root/dev +mount -t devpts devpts /mnt/pve-root/dev/pts + +# Chroot +chroot /mnt/pve-root + +# Try to repair +proxmox-boot-tool refresh +update-grub +update-initramfs -u + +# Exit chroot +exit + +# Unmount and reboot +umount -R /mnt/pve-root +reboot +``` + +### Scenario 3: ZFS Pool Recovery + +**Symptoms:** +- ZFS pool degraded +- Missing or failed disk in ZFS mirror/RAID + +**Recovery Procedure:** + +**1. Check Pool Status** +```bash +# Check ZFS pool health +zpool status + +# Example output showing degraded pool: +# pool: rpool +# state: DEGRADED +# scan: scrub in progress since... +``` + +**2. Replace Failed Disk in ZFS Mirror** +```bash +# Identify failed disk +zpool status rpool + +# Replace disk (assuming /dev/sdb failed, replacing with /dev/sdc) +zpool replace rpool /dev/sdb /dev/sdc + +# Monitor resilvering progress +watch zpool status rpool +``` + +**3. Import Pool from Backup Disks** +```bash +# If pool is not automatically imported +zpool import + +# Import specific pool +zpool import rpool + +# Force import if needed (use cautiously) +zpool import -f rpool +``` + +**4. Scrub Pool After Recovery** +```bash +# Start scrub to verify data integrity +zpool scrub rpool + +# Monitor scrub progress +zpool status +``` + +### Scenario 4: LVM Recovery + +**Symptoms:** +- LVM volume group issues +- Corrupted LVM metadata +- Missing physical volumes + +**Recovery Procedure:** + +**1. Scan for Volume Groups** +```bash +# Scan for all volume groups +vgscan + +# Activate all volume groups +vgchange -ay +``` + +**2. Restore LVM Metadata** +```bash +# LVM automatically backs up metadata to /etc/lvm/archive/ + +# List available metadata backups +ls -lh /etc/lvm/archive/ + +# Restore from backup +vgcfgrestore pve -f /etc/lvm/archive/pve_XXXXX.vg + +# Activate volume group +vgchange -ay pve +``` + +**3. Recover from Failed Disk** +```bash +# Remove failed physical volume from volume group +vgreduce pve /dev/sdX + +# Add new physical volume +pvcreate /dev/sdY +vgextend pve /dev/sdY + +# Move data from old to new disk (if old disk still readable) +pvmove /dev/sdX /dev/sdY +vgreduce pve /dev/sdX +``` + +### Scenario 5: Cluster Node Recovery + +**Symptoms:** +- Node removed from cluster +- Cluster quorum lost +- Split-brain scenario + +**Recovery Procedure:** + +**1. Check Cluster Status** +```bash +# Check cluster status +pvecm status + +# Check quorum +pvecm nodes +``` + +**2. Restore Single Node from Cluster** +```bash +# If node was removed from cluster and you want to use it standalone + +# Stop cluster services +systemctl stop pve-cluster +systemctl stop corosync + +# Start in local mode +pmxcfs -l + +# Remove cluster configuration +rm /etc/pve/corosync.conf +rm -rf /etc/corosync/* + +# Restart services +killall pmxcfs +systemctl start pve-cluster +``` + +**3. Rejoin Node to Cluster** +```bash +# On the node to be rejoined +pvecm add CLUSTER_NODE_IP + +# Enter cluster network information when prompted +# Node will rejoin cluster and sync configuration +``` + +**4. Recover Lost Quorum (Emergency Only)** +```bash +# If majority of cluster nodes are down and you need to continue +# WARNING: This can cause split-brain if other nodes come back + +# Set expected votes to current online nodes +pvecm expected 1 + +# This allows single node to have quorum temporarily +``` + +### Scenario 6: Configuration Recovery Without Backups + +**If /etc/pve/ is lost but VMs/containers intact:** + +**1. Identify Existing VMs/Containers** +```bash +# List LVM volumes +lvs + +# List ZFS datasets +zfs list -t all + +# VM disks typically in: +# LVM: pve/vm-XXX-disk-Y +# ZFS: rpool/data/vm-XXX-disk-Y +``` + +**2. Recreate VM Configuration** +```bash +# Create new VM with same VMID +qm create VMID --name "recovered-vm" --memory 4096 --cores 2 + +# Attach existing disk (LVM example) +qm set VMID --scsi0 local-lvm:vm-VMID-disk-0 + +# For ZFS +qm set VMID --scsi0 local-zfs:vm-VMID-disk-0 + +# Set other options as needed +qm set VMID --net0 virtio,bridge=vmbr0 +qm set VMID --boot c --bootdisk scsi0 + +# Try to start VM +qm start VMID +``` + +**3. Recreate Container Configuration** +```bash +# Containers are stored in /var/lib/vz/ or ZFS dataset +# Check for rootfs + +# Create container pointing to existing rootfs +pct create CTID /var/lib/vz/template/cache/[template].tar.gz \ + --rootfs local-lvm:vm-CTID-disk-0 \ + --hostname recovered-ct \ + --memory 2048 + +# Start container +pct start CTID +``` + +## Tools and Commands + +### Essential Proxmox Commands + +**VM Management:** +```bash +# List all VMs +qm list + +# Show VM config +qm config VMID + +# Start/stop VM +qm start VMID +qm stop VMID +qm shutdown VMID + +# Clone VM +qm clone VMID NEW_VMID --name new-vm-name + +# Migrate VM (in cluster) +qm migrate VMID TARGET_NODE +``` + +**Container Management:** +```bash +# List all containers +pct list + +# Show container config +pct config CTID + +# Start/stop container +pct start CTID +pct stop CTID +pct shutdown CTID + +# Enter container +pct enter CTID +``` + +**Storage Management:** +```bash +# List storage +pvesm status + +# Add storage +pvesm add [type] [storage-id] [options] + +# Scan for storage +pvesm scan [type] +``` + +**Backup/Restore:** +```bash +# Create backup +vzdump VMID --mode snapshot --compress zstd + +# Restore backup +qmrestore /path/to/backup.vma.zst NEW_VMID + +# List backups +pvesh get /nodes/NODE/storage/STORAGE/content --content backup +``` + +### Diagnostic Commands + +```bash +# Check Proxmox version +pveversion -v + +# Check system resources +pvesh get /nodes/NODE/status + +# Check running processes +pvesh get /nodes/NODE/tasks + +# Check logs +journalctl -u pve-cluster +journalctl -u pvedaemon +journalctl -u pveproxy + +# Check disk health +smartctl -a /dev/sdX + +# Check network +ip addr +ip route +``` + +### Recovery Tools + +**SystemRescue CD:** +- Boot from SystemRescue ISO +- Access to ZFS, LVM, and filesystem tools +- Can mount and repair Proxmox installations + +**Proxmox Live ISO:** +- Boot without installing +- Can mount existing installations +- Repair bootloader and configurations + +**TestDisk/PhotoRec:** +- Recover deleted files +- Repair partition tables + +## Preventive Measures + +### Regular Maintenance + +**1. Daily Checks** +```bash +# Check cluster/node status +pvecm status + +# Check VM/CT status +qm list +pct list + +# Check storage health +pvesm status +``` + +**2. Weekly Tasks** +```bash +# Update Proxmox +apt update && apt dist-upgrade + +# Check for failed systemd services +systemctl --failed + +# Review logs +journalctl -p err -b +``` + +**3. Monthly Tasks** +```bash +# Test backup restore +qmrestore [backup] 999 --storage local-lvm +qm start 999 +# Verify VM boots correctly +qm stop 999 +qm destroy 999 + +# Check disk health +for disk in /dev/sd?; do smartctl -H $disk; done + +# Check ZFS scrub +zpool scrub rpool +``` + +### Backup Best Practices + +**1. 3-2-1 Backup Strategy** +- 3 copies of data +- 2 different media types +- 1 off-site copy + +**2. Automated Backups** +- Schedule regular VM/CT backups +- Backup Proxmox configuration +- Test restore procedures regularly + +**3. Documentation** +- Keep network diagrams updated +- Document IP allocations +- Maintain runbooks for common tasks +- Store documentation off-site + +### Monitoring Setup + +**1. Setup Email Alerts** +```bash +# Configure postfix for email +apt install postfix + +# Test email +echo "Test" | mail -s "Proxmox Alert Test" your@email.com +``` + +**2. Monitor Resources** +- Set up monitoring for CPU, RAM, disk usage +- Alert on high resource consumption +- Monitor backup job success/failure + +**3. Health Checks** +```bash +# Create health check script +cat > /usr/local/bin/health-check.sh << 'EOF' +#!/bin/bash +# Proxmox Health Check + +# Check cluster status +if ! pvecm status &>/dev/null; then + echo "WARNING: Cluster status check failed" +fi + +# Check storage +pvesm status | grep -v active && echo "WARNING: Storage issue detected" + +# Check for failed VMs +qm list | grep stopped && echo "INFO: Stopped VMs detected" + +# Check system load +LOAD=$(cat /proc/loadavg | awk '{print $1}') +if (( $(echo "$LOAD > 8" | bc -l) )); then + echo "WARNING: High system load: $LOAD" +fi + +# Check disk space +df -h | awk '$5 ~ /^9[0-9]%/ || $5 ~ /^100%/ {print "WARNING: Disk space low on " $6 ": " $5}' +EOF + +chmod +x /usr/local/bin/health-check.sh + +# Add to crontab +echo "*/15 * * * * /usr/local/bin/health-check.sh | mail -s 'Proxmox Health Alert' your@email.com" | crontab - +``` + +## Emergency Contacts + +### Proxmox Resources +- Proxmox Forums: https://forum.proxmox.com/ +- Proxmox Documentation: https://pve.proxmox.com/pve-docs/ +- Proxmox Wiki: https://pve.proxmox.com/wiki/ + +### Hardware Support +- Document hardware vendor support contacts +- Keep warranty information accessible +- Maintain spare parts inventory + +## Recovery Time Objectives + +| Scenario | Target Recovery Time | Notes | +|----------|---------------------|-------| +| Single VM restore | 30 minutes | From local backup | +| Complete node rebuild | 4-8 hours | Including OS reinstall | +| ZFS pool recovery | 1-6 hours | Depends on resilvering time | +| Cluster rejoin | 1-2 hours | Network reconfiguration | +| Full disaster recovery | 24-48 hours | From off-site backups | + +## Recent Recovery Events + +### Event Log Template + +**Date:** YYYY-MM-DD +**Affected System:** [Proxmox node/VM/CT] +**Issue:** [Description] +**Resolution:** [Steps taken] +**Downtime:** [Duration] +**Lessons Learned:** [Improvements for next time] + +--- + +**Last Updated:** 2025-12-13 +**Version:** 1.0 diff --git a/infrastructure/README.md b/infrastructure/README.md new file mode 100644 index 0000000..5082867 --- /dev/null +++ b/infrastructure/README.md @@ -0,0 +1,174 @@ +# Home Infrastructure + +Comprehensive documentation and configuration management for home network infrastructure, Home Assistant, and smart home automation. + +## Repository Structure + +``` +infrastructure/ +โ”œโ”€โ”€ home-assistant/ # Home Assistant configuration files +โ”‚ โ”œโ”€โ”€ configuration.yaml +โ”‚ โ”œโ”€โ”€ automations.yaml +โ”‚ โ”œโ”€โ”€ scripts.yaml +โ”‚ โ”œโ”€โ”€ switches.yaml +โ”‚ โ””โ”€โ”€ secrets.yaml (gitignored) +โ”‚ +โ”œโ”€โ”€ esphome/ # ESPHome device configurations +โ”‚ โ”œโ”€โ”€ garage-controller.yaml +โ”‚ โ””โ”€โ”€ README.md +โ”‚ +โ”œโ”€โ”€ voice-assistant/ # Local voice assistant system +โ”‚ โ”œโ”€โ”€ gaming-pc-setup/ # Docker services (GPU-accelerated AI) +โ”‚ โ”œโ”€โ”€ surface-go-setup/ # Wyoming satellite installation +โ”‚ โ”œโ”€โ”€ home-assistant-config/ # Voice pipeline HA config (merge with main) +โ”‚ โ”œโ”€โ”€ docs/ # Voice system documentation +โ”‚ โ”œโ”€โ”€ README.md # Voice system overview +โ”‚ โ””โ”€โ”€ QUICK_START.md # 30-minute voice setup guide +โ”‚ +โ”œโ”€โ”€ docs/ # Infrastructure documentation +โ”‚ โ”œโ”€โ”€ FURNACE-PROJECT.md # Furnace control integration project +โ”‚ โ”œโ”€โ”€ HOME-ASSISTANT-CONFIG-MERGE.md # Guide to merge voice HA config +โ”‚ โ”œโ”€โ”€ MQTT-SETUP.md # MQTT broker configuration +โ”‚ โ”œโ”€โ”€ DNS-OVER-TLS-SETUP.md # DNS security setup +โ”‚ โ”œโ”€โ”€ MONITORING.md # System monitoring setup +โ”‚ โ”œโ”€โ”€ SERVICES.md # Services inventory +โ”‚ โ”œโ”€โ”€ DISASTER-RECOVERY.md # Backup and recovery procedures +โ”‚ โ”œโ”€โ”€ RUNBOOK.md # Operational procedures +โ”‚ โ””โ”€โ”€ IMPROVEMENTS.md # Future improvements tracking +โ”‚ +โ”œโ”€โ”€ scripts/ # Automation and utility scripts +โ”‚ +โ””โ”€โ”€ claude-shared/ # Shared resources with Claude Code assistant +``` + +## Quick Start + +### Home Assistant +Configuration files are in `/home-assistant/`. + +**Key Files:** +- `configuration.yaml` - Main HA configuration +- `automations.yaml` - All automations +- `scripts.yaml` - Reusable scripts +- `secrets.yaml` - Sensitive data (create from secrets.yaml.example) + +**Deployment:** +Copy files to your Home Assistant config directory (typically `/config/` in HA OS). + +### ESPHome Devices +Device configurations are in `/esphome/`. + +**Current Devices:** +- **garage-controller** - ESP32 with 8-relay board controlling garage doors, lights, and planned furnace integration + +**Deployment:** +```bash +cd esphome +esphome run garage-controller.yaml +``` + +Or use Home Assistant ESPHome integration for OTA updates. + +### Voice Assistant + +**Local GPU-accelerated voice assistant system** with Home Assistant integration. + +**System Components:** +- **Gaming PC (RTX 5060):** Docker containers for Ollama LLM, Whisper STT, Piper TTS, OpenWakeWord +- **Home Assistant:** Voice pipeline coordinator +- **Surface Go:** Wyoming satellite (microphone/speaker interface) + +**Quick Start:** +1. Set up Gaming PC AI services: `/voice-assistant/gaming-pc-setup/` +2. Set up Surface Go satellite: `/voice-assistant/surface-go-setup/` +3. Merge voice HA config: See `/docs/HOME-ASSISTANT-CONFIG-MERGE.md` +4. Full documentation: `/voice-assistant/README.md` + +**Current Status:** Operational - needs HA config merge for full integration + +## Active Projects + +### ๐Ÿ”ฅ Furnace Control Integration +- **Status:** Planning phase +- **Goal:** Replace failed furnace board with ESP32-based smart control +- **Documentation:** [docs/FURNACE-PROJECT.md](docs/FURNACE-PROJECT.md) +- **Hardware:** ESP32-WROOM-32E with relay board, 6x temp/humidity sensors +- **Features:** Multi-zone monitoring (garage + shed), HA integration, safety interlocks + +### ๐ŸŽค Local Voice Assistant +- **Status:** Operational, pending HA config merge +- **Goal:** GPU-accelerated local voice control with LLM +- **Documentation:** [voice-assistant/README.md](voice-assistant/README.md) +- **Hardware:** Gaming PC (RTX 5060), Surface Go (Wyoming satellite) +- **Features:** Ollama LLM, Whisper STT, Piper TTS, OpenWakeWord, no cloud dependencies +- **Integration:** See [docs/HOME-ASSISTANT-CONFIG-MERGE.md](docs/HOME-ASSISTANT-CONFIG-MERGE.md) + +### ๐ŸŒ Network Infrastructure +- **MQTT:** Mosquitto broker for device communication +- **DNS:** DNS-over-TLS with Unbound +- **Documentation:** See docs/MQTT-SETUP.md and docs/DNS-OVER-TLS-SETUP.md + +## Documentation + +All infrastructure documentation is in the `/docs/` directory: + +- **[FURNACE-PROJECT.md](docs/FURNACE-PROJECT.md)** - ESP32 furnace control integration +- **[HOME-ASSISTANT-CONFIG-MERGE.md](docs/HOME-ASSISTANT-CONFIG-MERGE.md)** - Merge voice assistant HA config +- **[MQTT-SETUP.md](docs/MQTT-SETUP.md)** - MQTT broker setup and configuration +- **[DNS-OVER-TLS-SETUP.md](docs/DNS-OVER-TLS-SETUP.md)** - Secure DNS configuration +- **[MONITORING.md](docs/MONITORING.md)** - System monitoring and alerting +- **[SERVICES.md](docs/SERVICES.md)** - Inventory of all services +- **[DISASTER-RECOVERY.md](docs/DISASTER-RECOVERY.md)** - Backup and recovery procedures +- **[RUNBOOK.md](docs/RUNBOOK.md)** - Operational procedures and troubleshooting + +**Voice assistant documentation** is in `/voice-assistant/docs/`: +- Installation, commands, calendar setup, troubleshooting + +## Network Information + +Current network setup and IP allocations are documented in: +- [IP-ALLOCATION.md](IP-ALLOCATION.md) - IP address assignments +- DHCP exports in repository root + +## Contributing + +This is a personal infrastructure repository. Updates are made through: +1. Local testing and validation +2. Git commits with descriptive messages +3. Push to GitHub for backup and history + +## Secrets Management + +Sensitive information (passwords, API keys, etc.) is stored in `secrets.yaml` files which are gitignored. + +**Template files:** +- `/home-assistant/secrets.yaml.example` +- Create your own `secrets.yaml` from the template + +## Backup Strategy + +See [DISASTER-RECOVERY.md](docs/DISASTER-RECOVERY.md) for: +- Backup procedures +- Recovery steps +- Critical system information + +## Support & Notes + +- **Experience Level:** 20+ years low voltage wiring, network infrastructure +- **Tools:** Home Assistant, ESPHome, MQTT, Unbound DNS +- **Approach:** Document everything, safety-first, incremental improvements + +## Recent Updates + +- **2025-11-28:** + - Consolidated all smart home projects into monorepo + - Added ESPHome directory and furnace control project documentation + - Integrated voice assistant project (Gaming PC AI + Surface Go) + - Created Home Assistant config merge guide +- **2025-11-27:** Home Assistant configuration updates +- **2025-11-18:** MQTT and DNS-over-TLS setup documentation + +--- + +*Maintained by: Fred N9MRQ* +*Repository: https://github.com/FredN9MRQ/infrastructure* diff --git a/infrastructure/RUNBOOK.md b/infrastructure/RUNBOOK.md new file mode 100644 index 0000000..df28acc --- /dev/null +++ b/infrastructure/RUNBOOK.md @@ -0,0 +1,479 @@ +# Infrastructure Runbook + +This runbook provides step-by-step procedures for common operational tasks in your infrastructure. + +## Table of Contents +- [Pangolin Reverse Proxy Operations](#pangolin-reverse-proxy-operations) +- [Gerbil Tunnel Management](#gerbil-tunnel-management) +- [Proxmox Operations](#proxmox-operations) +- [SSL/TLS Certificate Management](#ssltls-certificate-management) +- [Network Troubleshooting](#network-troubleshooting) +- [Security Procedures](#security-procedures) +- [Backup Operations](#backup-operations) + +--- + +## Pangolin Reverse Proxy Operations + +### Add a New Route +```bash +# 1. SSH into VPS +ssh user@your-vps-ip + +# 2. Edit Pangolin configuration +sudo nano /path/to/pangolin/config.yml + +# 3. Add new route configuration +# domain.example.com -> backend:port + +# 4. Test configuration +sudo pangolin config test + +# 5. Reload Pangolin +sudo systemctl reload pangolin +# OR +sudo pangolin reload + +# 6. Verify route is active +curl -I https://domain.example.com +``` + +### Remove a Route +```bash +# 1. Edit configuration and comment out or remove route +sudo nano /path/to/pangolin/config.yml + +# 2. Reload Pangolin +sudo systemctl reload pangolin + +# 3. Verify route is removed +curl -I https://domain.example.com +``` + +### View Pangolin Logs +```bash +# Real-time logs +sudo tail -f /var/log/pangolin/access.log +sudo tail -f /var/log/pangolin/error.log + +# Search for specific domain +grep "domain.example.com" /var/log/pangolin/access.log + +# Check last 100 errors +sudo tail -n 100 /var/log/pangolin/error.log +``` + +### Restart Pangolin Service +```bash +# Check status +sudo systemctl status pangolin + +# Restart +sudo systemctl restart pangolin + +# Verify it's running +sudo systemctl is-active pangolin +``` + +--- + +## Gerbil Tunnel Management + +### Check Active Tunnels +```bash +# On VPS - check listening Gerbil server +ss -tlnp | grep gerbil + +# On home lab - check active tunnel connections +gerbil status +# OR +ps aux | grep gerbil +``` + +### Start a Tunnel +```bash +# On home lab machine +gerbil connect --name tunnel-name \ + --local localhost:PORT \ + --remote VPS_IP:REMOTE_PORT \ + --auth-key /path/to/auth.key + +# Start as systemd service +sudo systemctl start gerbil-tunnel-name +``` + +### Stop a Tunnel +```bash +# If running as service +sudo systemctl stop gerbil-tunnel-name + +# If running manually +pkill -f "gerbil.*tunnel-name" +``` + +### Restart a Tunnel +```bash +sudo systemctl restart gerbil-tunnel-name + +# Verify tunnel is active +gerbil status tunnel-name +# OR +ss -tn | grep REMOTE_PORT +``` + +### Debug Tunnel Connection Issues +```bash +# 1. Check if local service is running +curl http://localhost:LOCAL_PORT + +# 2. Check if tunnel process is running +ps aux | grep gerbil + +# 3. Check tunnel logs +journalctl -u gerbil-tunnel-name -n 50 + +# 4. Test VPS endpoint +# On VPS: +curl http://localhost:REMOTE_PORT + +# 5. Check firewall on VPS +sudo ufw status +sudo iptables -L -n | grep REMOTE_PORT +``` + +--- + +## Proxmox Operations + +### Create a New VM +```bash +# Via Proxmox web UI: https://PROXMOX_IP:8006 + +# Via CLI on Proxmox node: +qm create VMID --name vm-name --memory 2048 --cores 2 --net0 virtio,bridge=vmbr0 + +# Attach disk +qm set VMID --scsi0 local-lvm:32 + +# Set boot order +qm set VMID --boot order=scsi0 + +# Start VM +qm start VMID +``` + +### Create a New Container (LXC) +```bash +# Download template +pveam update +pveam available +pveam download local ubuntu-22.04-standard + +# Create container +pct create CTID local:vztmpl/ubuntu-22.04-standard.tar.gz \ + --hostname ct-name \ + --memory 1024 \ + --cores 2 \ + --net0 name=eth0,bridge=vmbr0,ip=dhcp + +# Start container +pct start CTID + +# Enter container +pct enter CTID +``` + +### Stop/Start VM or Container +```bash +# VM operations +qm stop VMID # Stop +qm start VMID # Start +qm shutdown VMID # Graceful shutdown +qm reboot VMID # Reboot +qm status VMID # Check status + +# Container operations +pct stop CTID +pct start CTID +pct shutdown CTID +pct reboot CTID +pct status CTID +``` + +### Migrate VM Between Nodes +```bash +# Online migration (VM stays running) +qm migrate VMID target-node --online + +# Offline migration +qm migrate VMID target-node + +# Check migration status +qm status VMID +``` + +### Check Resource Usage +```bash +# Overall cluster resources +pvesh get /cluster/resources + +# Specific node resources +pvesh get /nodes/NODE_NAME/status + +# VM resource usage +qm status VMID --verbose + +# Storage usage +pvesm status +``` + +### Backup VM or Container +```bash +# Backup VM +vzdump VMID --storage STORAGE_NAME --mode snapshot + +# Backup container +vzdump CTID --storage STORAGE_NAME + +# List backups +pvesm list STORAGE_NAME +``` + +### Restore from Backup +```bash +# Restore VM +qmrestore /path/to/backup/vzdump-qemu-VMID.vma.zst VMID + +# Restore container +pct restore CTID /path/to/backup/vzdump-lxc-CTID.tar.zst +``` + +--- + +## SSL/TLS Certificate Management + +### Request New Let's Encrypt Certificate +```bash +# Install certbot if needed +sudo apt install certbot + +# Request certificate (HTTP-01 challenge) +sudo certbot certonly --standalone -d domain.example.com + +# Request wildcard certificate (DNS-01 challenge) +sudo certbot certonly --manual --preferred-challenges dns -d "*.example.com" + +# Certificates are stored in: /etc/letsencrypt/live/domain.example.com/ +``` + +### Renew Certificates +```bash +# Dry run to test renewal +sudo certbot renew --dry-run + +# Renew all certificates +sudo certbot renew + +# Renew specific certificate +sudo certbot renew --cert-name domain.example.com + +# Set up auto-renewal (check if already configured) +sudo systemctl status certbot.timer +``` + +### Check Certificate Expiration +```bash +# Check local certificate +sudo certbot certificates + +# Check remote certificate +echo | openssl s_client -servername domain.example.com -connect domain.example.com:443 2>/dev/null | openssl x509 -noout -dates + +# Check all certificates expiring in 30 days +sudo certbot certificates | grep "Expiry Date" +``` + +### Deploy Certificate to Service +```bash +# Copy certificate to service location +sudo cp /etc/letsencrypt/live/domain.example.com/fullchain.pem /path/to/service/cert.pem +sudo cp /etc/letsencrypt/live/domain.example.com/privkey.pem /path/to/service/key.pem + +# Set permissions +sudo chmod 644 /path/to/service/cert.pem +sudo chmod 600 /path/to/service/key.pem + +# Reload service +sudo systemctl reload service-name +``` + +--- + +## Network Troubleshooting + +### Check Network Connectivity +```bash +# Ping test +ping -c 4 8.8.8.8 + +# DNS resolution +nslookup domain.example.com +dig domain.example.com + +# Trace route +traceroute domain.example.com +mtr domain.example.com +``` + +### Check Open Ports +```bash +# Check listening ports +ss -tlnp +netstat -tlnp + +# Check if specific port is open +ss -tlnp | grep :PORT +nc -zv localhost PORT + +# Check firewall rules +sudo ufw status numbered +sudo iptables -L -n -v +``` + +### Test Service Availability +```bash +# HTTP/HTTPS test +curl -I https://domain.example.com +curl -v https://domain.example.com + +# Test specific port +nc -zv host PORT +telnet host PORT + +# Check service status +sudo systemctl status service-name +``` + +### Check Network Interface Status +```bash +# List all interfaces +ip addr show +ip link show + +# Check interface statistics +ip -s link show eth0 + +# Restart interface +sudo ip link set eth0 down +sudo ip link set eth0 up +``` + +--- + +## Security Procedures + +### Update SSH Key +```bash +# Generate new SSH key +ssh-keygen -t ed25519 -C "description" + +# Copy to server +ssh-copy-id -i ~/.ssh/new_key.pub user@server + +# Test new key +ssh -i ~/.ssh/new_key user@server + +# Update SSH config +nano ~/.ssh/config +``` + +### Review Failed Login Attempts +```bash +# Check auth logs +sudo grep "Failed password" /var/log/auth.log +sudo journalctl -u ssh -n 100 + +# Check fail2ban status (if installed) +sudo fail2ban-client status sshd +``` + +### Update Firewall Rules +```bash +# Add new rule +sudo ufw allow PORT/tcp +sudo ufw allow from IP_ADDRESS to any port PORT + +# Remove rule +sudo ufw delete allow PORT/tcp +sudo ufw status numbered +sudo ufw delete NUMBER + +# Reload firewall +sudo ufw reload +``` + +### Security Updates +```bash +# Check for updates +sudo apt update +sudo apt list --upgradable + +# Install security updates only +sudo apt upgrade -y + +# Reboot if kernel updated +sudo needrestart -r a +``` + +--- + +## Backup Operations + +### Manual Backup +```bash +# Backup specific VM/Container +vzdump VMID --storage STORAGE_NAME --mode snapshot --compress zstd + +# Backup configuration files +tar -czf config-backup-$(date +%Y%m%d).tar.gz /etc/pangolin /etc/gerbil + +# Backup to remote location +rsync -avz /path/to/data/ user@backup-server:/path/to/backup/ +``` + +### Verify Backup +```bash +# List backup contents +tar -tzf backup.tar.gz | less + +# Check backup integrity +tar -tzf backup.tar.gz > /dev/null && echo "OK" || echo "CORRUPTED" + +# Check vzdump backup +cat /path/to/backup/vzdump-qemu-VMID.log +``` + +### Restore Specific Files +```bash +# Extract specific file from backup +tar -xzf backup.tar.gz path/to/specific/file + +# Restore from rsync backup +rsync -avz user@backup-server:/path/to/backup/ /path/to/restore/ +``` + +--- + +## Emergency Contacts + +- Infrastructure Owner: _______________ +- Network Administrator: _______________ +- VPS Provider Support: _______________ +- DNS Provider Support: _______________ + +## Additional Resources + +- Pangolin Documentation: _______________ +- Gerbil Documentation: _______________ +- Proxmox Documentation: https://pve.proxmox.com/pve-docs/ +- Internal Wiki: _______________ diff --git a/infrastructure/SERVICES.md b/infrastructure/SERVICES.md new file mode 100644 index 0000000..c53a619 --- /dev/null +++ b/infrastructure/SERVICES.md @@ -0,0 +1,1120 @@ +# Services Documentation + +**Last Updated:** 2025-12-29 +**Status:** All critical services operational + +This document provides detailed information about all services running in the infrastructure. + +## Table of Contents +- [Service Overview](#service-overview) +- [VPS Services](#vps-services) +- [Home Lab Services](#home-lab-services) +- [Service Dependencies](#service-dependencies) +- [Monitoring & Health Checks](#monitoring--health-checks) + +--- + +## Service Overview + +### Service Inventory Summary + +| Service Name | Location | IP Address | Type | Status | Critical | +|--------------|----------|------------|------|--------|----------| +| UCG Ultra Gateway | Home Lab | 10.0.10.1 | Network | โœ… Running | Yes | +| Proxmox (main-pve) | Home Lab | 10.0.10.3 | Virtualization | โœ… Running | Yes | +| Proxmox (pve-router) | Home Lab | 10.0.10.2 | Virtualization | โœ… Running | Yes | +| Proxmox (pve-storage) | Home Lab | 10.0.10.4 | Virtualization | โœ… Running | Yes | +| OpenMediaVault | Home Lab | 10.0.10.5 | Storage | โœ… Running | Yes | +| PostgreSQL | Home Lab | 10.0.10.20 | Database | โœ… Running | Yes | +| Authentik SSO | Home Lab | 10.0.10.21 | Authentication | โœ… Running | Yes | +| n8n | Home Lab | 10.0.10.22 | Automation | โœ… Running | No | +| Home Assistant | Home Lab | 10.0.10.24 | Smart Home | โœ… Running | No | +| Prometheus + Grafana | Home Lab | 10.0.10.25 | Monitoring | โœ… Running | No | +| Dockge | Home Lab | 10.0.10.27 | Container Mgmt | โœ… Running | No | +| Sonarr | Home Lab | 10.0.10.27 | Media | โœ… Running | No | +| Radarr | Home Lab | 10.0.10.27 | Media | โœ… Running | No | +| Prowlarr | Home Lab | 10.0.10.27 | Media | โœ… Running | No | +| Bazarr | Home Lab | 10.0.10.27 | Media | โœ… Running | No | +| Deluge | Home Lab | 10.0.10.27 | Media | โœ… Running | No | +| Calibre-Web | Home Lab | 10.0.10.27 | Media | โœ… Running | No | +| Caddy Internal Proxy | Home Lab | 10.0.10.27 | Proxy | โœ… Running | No | +| Vehicle Tracker | Home Lab | 10.0.10.35 | Web App | ๐Ÿ”„ In Development | No | +| RustDesk ID Server | Home Lab | 10.0.10.23 | Remote Desktop | โœ… Running | No | +| RustDesk Relay | VPS | 66.63.182.168 | Remote Desktop | โœ… Running | No | +| OpenClaw Gateway | Home Lab | 10.0.10.28 | AI Agent | โœ… Running | No | +| AD5M 3D Printer | Home Lab | 10.0.10.30 | IoT | โœ… Running | No | +| WireGuard VPN | Gaming VPS | 51.222.12.162 | Tunnel | โœ… Running | Yes | +| Caddy Reverse Proxy | VPS | 66.63.182.168 | Proxy | โœ… Running | Yes | + +--- + +## VPS Services + +### WireGuard VPN Server + +**Purpose**: Site-to-site VPN tunnel connecting VPS and home lab network + +**Service Details**: +- **Host**: Gaming VPS (51.222.12.162, deadeyeg4ming.vip) +- **Service Name**: `wg-quick@wg0` +- **Port**: 51820/UDP +- **Interface**: wg0 +- **Tunnel IP**: 10.0.9.1/24 +- **Configuration**: `/etc/wireguard/wg0.conf` +- **Logs**: `journalctl -u wg-quick@wg0` +- **Peers**: UCG Ultra at 10.0.9.2, VPS Proxy at 10.0.9.3 + +**Configuration**: +```ini +[Interface] +Address = 10.0.9.1/24 +ListenPort = 51820 +PrivateKey = [REDACTED] + +PostUp = iptables -A FORWARD -i wg0 -j ACCEPT +PostUp = iptables -A FORWARD -o wg0 -j ACCEPT +PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE +PostDown = iptables -D FORWARD -i wg0 -j ACCEPT +PostDown = iptables -D FORWARD -o wg0 -j ACCEPT +PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE + +[Peer] +PublicKey = [UCG Ultra public key] +AllowedIPs = 10.0.9.2/32, 10.0.10.0/24 + +[Peer] +PublicKey = [VPS Proxy public key] +AllowedIPs = 10.0.9.3/32 +``` + +**Startup**: +```bash +sudo systemctl start wg-quick@wg0 +sudo systemctl enable wg-quick@wg0 +``` + +**Health Check**: +```bash +sudo wg show +sudo systemctl status wg-quick@wg0 +ping 10.0.9.2 # UCG Ultra tunnel IP +``` + +**Status**: โœ… Operational - Tunnel stable, traffic flowing + +--- + +### Caddy Reverse Proxy + +**Purpose**: Routes incoming HTTPS traffic to home lab services via WireGuard tunnel + +**Service Details**: +- **Host**: VPS (66.63.182.168) +- **Service Name**: `caddy` +- **Port(s)**: 80 (HTTP), 443 (HTTPS) +- **Configuration**: `/etc/caddy/Caddyfile` +- **Logs**: `journalctl -u caddy` +- **SSL**: Automatic via Let's Encrypt + +**Current Routes**: +```caddyfile +# Proxmox (main-pve) +freddesk.nianticbooks.com { + reverse_proxy https://10.0.10.3:8006 { + header_up Host {http.reverse_proxy.upstream.hostport} + header_up X-Forwarded-Host {host} + header_up X-Forwarded-Proto {scheme} + header_up X-Real-IP {remote_host} + header_up X-Forwarded-For {remote_host} + + transport http { + tls_insecure_skip_verify + } + } +} + +# Home Assistant +bob.nianticbooks.com { + reverse_proxy https://10.0.10.24:8123 { + header_up X-Forwarded-Host {host} + header_up X-Forwarded-Proto {scheme} + header_up X-Real-IP {remote_host} + header_up X-Forwarded-For {remote_host} + + transport http { + tls_insecure_skip_verify + } + } +} + +# 3D Printer (Prusa AD5M) +ad5m.nianticbooks.com { + reverse_proxy 10.0.10.30:80 +} + +# Authentik SSO +auth.nianticbooks.com { + reverse_proxy 10.0.10.21:9000 +} + +# M'Cheyne Bible Reading Plan +bible.nianticbooks.com { + reverse_proxy localhost:8081 + encode gzip + + header { + Strict-Transport-Security "max-age=31536000" + X-Frame-Options "SAMEORIGIN" + X-Content-Type-Options "nosniff" + } +} +``` + +**Startup**: +```bash +sudo systemctl start caddy +sudo systemctl enable caddy +sudo systemctl reload caddy # After config changes +``` + +**Health Check**: +```bash +sudo systemctl status caddy +curl -I https://freddesk.nianticbooks.com +curl -I https://ad5m.nianticbooks.com +``` + +**Status**: โœ… Operational - All 5 public domains working + +--- + +## Home Lab Services + +### UCG Ultra Gateway + +**Purpose**: Network gateway, DHCP server, firewall, WireGuard VPN client + +**Service Details**: +- **IP Address**: 10.0.10.1 +- **DHCP Range**: 10.0.10.50-254 +- **Static Range**: 10.0.10.1-49 +- **WireGuard Interface**: wgclt1 (10.0.9.2/24) +- **Web Interface**: https://10.0.10.1 + +**Health Check**: +```bash +ping 10.0.10.1 +ssh root@10.0.10.1 "ip a show wgclt1" # Check WireGuard interface +``` + +**Status**: โœ… Operational + +--- + +### Proxmox Cluster + +#### main-pve (DL380p - Primary Node) + +**Purpose**: Production workload virtualization (32 cores, 96GB RAM) + +**Service Details**: +- **IP Address**: 10.0.10.3 +- **Web Interface**: https://10.0.10.3:8006 +- **Public Domain**: https://freddesk.nianticbooks.com +- **iLO Management**: 10.0.10.13 +- **Location**: Remote (not in office) +- **Version**: Proxmox VE 8.x +- **SSO**: Authentik via OpenID Connect + +**Running VMs/Containers**: +- CT 102: PostgreSQL (10.0.10.20) +- CT 121: Authentik SSO (10.0.10.21) +- CT 106: n8n (10.0.10.22) +- CT 127: Dockge (10.0.10.27) +- VM 103: Home Assistant (10.0.10.24) +- Additional containers: See Proxmox web UI for complete list + +**Health Check**: +```bash +ping 10.0.10.3 +curl -k -I https://10.0.10.3:8006 +ssh root@10.0.10.3 "pveversion" +``` + +**Status**: โœ… Operational + +--- + +#### pve-router (i5 - Secondary Node) + +**Purpose**: Local development, secondary workloads (8 cores, 8GB RAM) + +**Service Details**: +- **IP Address**: 10.0.10.2 +- **DNS**: proxmox.nianticbooks.home +- **MAC**: e4:54:e8:50:90:af +- **Web Interface**: https://10.0.10.2:8006 +- **Location**: Office (local access available) +- **SSO**: Authentik via OpenID Connect + +**Running VMs/Containers**: +- CT 100: pve-scripts-local (10.0.10.40) +- CT 101: Twingate connector + +**Health Check**: +```bash +ping 10.0.10.2 +curl -k -I https://10.0.10.2:8006 +``` + +**Status**: โœ… Operational + +--- + +#### pve-storage (Storage Host) + +**Purpose**: Hosts OMV storage VM (3.5" drive support) + +**Service Details**: +- **Proxmox IP**: 10.0.10.4 +- **OMV VM IP**: 10.0.10.5 +- **Storage**: 12TB +- **Form Factor**: Supports 3.5" drives (unique among nodes) +- **SSO**: Authentik via OpenID Connect + +**Health Check**: +```bash +ping 10.0.10.4 +ping 10.0.10.5 +``` + +**Status**: โœ… Operational + +--- + +### OpenMediaVault (12TB Storage) + +**Purpose**: Centralized storage for backups, shared data, Proxmox storage + +**Service Details**: +- **IP Address**: 10.0.10.5 +- **MAC**: bc:24:11:a8:ff:0b +- **Web Interface**: http://10.0.10.5 +- **Capacity**: 12TB +- **NFS Share**: /export/backups mounted on Proxmox nodes + +**Shares**: +- `/export/backups` - Proxmox VM/container backups (NFS) +- Available: 7.3TB, Used: 159GB + +**Mount Points** (on Proxmox nodes): +```bash +# /etc/fstab entries on main-pve, pve-router, pve-storage +10.0.10.5:/export/backups /mnt/omv-backups nfs rsize=32768,wsize=32768,vers=3,tcp,timeo=600,retrans=2,_netdev 0 0 +``` + +**Health Check**: +```bash +ping 10.0.10.5 +ssh root@main-pve "df -h /mnt/omv-backups" +showmount -e 10.0.10.5 +``` + +**Status**: โœ… Operational - 7.3TB available, 159GB used + +--- + +### PostgreSQL (Shared Database) + +**Purpose**: Centralized database for Authentik, n8n, RustDesk, Grafana + +**Service Details**: +- **Host**: main-pve (CT 102) +- **IP Address**: 10.0.10.20 +- **Version**: PostgreSQL 16 +- **Port**: 5432 +- **Databases**: authentik, n8n, rustdesk (planned), grafana (planned) + +**Configuration**: +- Listen address: 0.0.0.0 (accessible from LAN) +- Max connections: 100 +- Shared buffers: 256MB + +**Startup**: +```bash +pct exec 102 -- systemctl status postgresql +pct exec 102 -- systemctl restart postgresql +``` + +**Health Check**: +```bash +ping 10.0.10.20 +pct exec 102 -- sudo -u postgres psql -c "SELECT version();" +``` + +**Backup**: +- Automated daily backup at 2:00 AM +- Script: `/usr/local/bin/backup-postgresql.sh` +- Location: /mnt/omv-backups/postgres/ +- Retention: 7 days + +**Status**: โœ… Operational + +--- + +### Authentik SSO + +**Purpose**: Single sign-on authentication for all services + +**Service Details**: +- **Host**: main-pve (CT 121) +- **IP Address**: 10.0.10.21 +- **Port**: 9000 (HTTP) +- **Version**: 2025.10.2 +- **Database**: PostgreSQL on 10.0.10.20 +- **Public Domain**: https://auth.nianticbooks.com (planned) + +**Configuration**: +- Admin User: akadmin +- API Token: f7AsYT6FLZEWVvmN59lC0IQZfMLdgMniVPYhVwmYAFSKHez4aGxyn4Esm86r +- Database: authentik @ 10.0.10.20 +- Secret Key: [REDACTED] + +**Active Integrations**: +- โœ… Proxmox (main-pve, pve-router, pve-storage) via OpenID Connect + - Client ID: proxmox + - Login: Select "authentik" realm โ†’ "Login with authentik" button +- โœ… Grafana (OAuth2 configured) + +**Planned Integrations**: +- Home Assistant (complex - requires proxy provider or LDAP) +- Other services as needed + +**Startup**: +```bash +pct exec 121 -- docker compose -f /opt/authentik/docker-compose.yml ps +pct exec 121 -- docker compose -f /opt/authentik/docker-compose.yml restart +``` + +**Health Check**: +```bash +curl -I http://10.0.10.21:9000 +``` + +**Status**: โœ… Operational - Proxmox SSO working + +--- + +### n8n Workflow Automation + +**Purpose**: Workflow automation and Claude Code integration + +**Service Details**: +- **Host**: main-pve (CT 106) +- **IP Address**: 10.0.10.22 +- **Port**: 5678 (HTTP) +- **Version**: 1.123.5 +- **Database**: PostgreSQL on 10.0.10.20 +- **Resources**: 2 vCPUs, 4GB RAM + +**Configuration**: +- Docker-based deployment +- Database: n8n @ 10.0.10.20 +- Authentication: Email/password (OIDC requires Enterprise license) + +**Claude Code Integration**: +- Architecture: n8n โ†’ SSH โ†’ Claude Code on HOMELAB-COMMAND (10.0.10.10) +- SSH Credential: homelab-command-ssh +- Test workflow: "Claude Code Test" โœ… Verified working +- Use cases: Infrastructure automation, AI workflows + +**Startup**: +```bash +pct exec 106 -- docker ps +pct exec 106 -- docker restart n8n +``` + +**Health Check**: +```bash +curl -I http://10.0.10.22:5678 +``` + +**Status**: โœ… Operational - Basic Claude Code integration working + +**See Also**: N8N-CLAUDE-STATUS.md for integration details + +--- + +### Home Assistant + +**Purpose**: Smart home automation and device control + +**Service Details**: +- **Host**: main-pve (VM 103) +- **IP Address**: 10.0.10.24 +- **Port**: 8123 (HTTPS) +- **MAC**: 02:f5:e9:54:36:28 +- **Public Domain**: https://bob.nianticbooks.com โœ… Working + +**Integrations**: +- Govee Curtain Lights (Local LAN control) +- Sylvania Smart+ WiFi Plug via LocalTuya +- Digital Loggers Web Power Switch (10.0.10.88) +- Wyoming Protocol voice assistant (Gaming PC 10.0.10.10) +- ESPHome (runs as HA add-on) +- Weather (Met.no) +- Local Todo lists + +**Configuration**: +- SSL Certificate: Local CA certificate +- Trusted Proxies: 127.0.0.1, 10.0.9.3 (VPS Proxy WireGuard IP) + +**Startup**: +```bash +# Via Proxmox +qm status 103 +qm start 103 +qm shutdown 103 + +# Inside VM +ssh root@10.0.10.24 "ha core restart" +``` + +**Health Check**: +```bash +ping 10.0.10.24 +curl -k -I https://10.0.10.24:8123 +``` + +**Status**: โœ… Operational - Accessible locally and publicly via HTTPS + +**See Also**: home-assistant/ directory for configuration files + +--- + +### Prometheus + Grafana (Monitoring) + +**Purpose**: Infrastructure monitoring, metrics collection, and visualization + +**Service Details**: +- **Host**: main-pve (10.0.10.25) +- **Grafana Port**: 3000 (HTTP) +- **Prometheus Port**: 9090 (HTTP) +- **Type**: VM or Container (TBD - need to verify) + +**Components**: +- **Grafana**: Visualization and dashboards +- **Prometheus**: Metrics collection and storage +- **Node Exporter**: Host metrics (planned) +- **cAdvisor**: Container metrics (planned) + +**Startup**: +```bash +# TBD - depends on deployment method (Docker Compose, systemd, etc.) +# Check status +curl -I http://10.0.10.25:3000 # Grafana +curl -I http://10.0.10.25:9090 # Prometheus +``` + +**Health Check**: +```bash +ping 10.0.10.25 +curl -I http://10.0.10.25:3000 # Grafana (redirects to /login) +curl -I http://10.0.10.25:9090 # Prometheus +``` + +**Status**: โœ… Operational - Both services responding + +**Configuration**: +- Database: TBD (may use PostgreSQL on 10.0.10.20) +- SSO: Authentik integration planned +- Public Domain: Not configured (internal access only) + +**Notes**: +- Discovered already deployed during infrastructure audit (2025-12-29) +- Need to document deployment method and configuration details +- Need to set up monitoring targets (Proxmox, VPS, services) +- Dashboards and alerting not yet configured + +--- + +### Dockge (Container Management) + +**Purpose**: Docker Compose stack management with web UI + +**Service Details**: +- **Host**: main-pve (CT 127) +- **IP Address**: 10.0.10.27 +- **Port**: 5001 (HTTP) +- **Resources**: 2 vCPUs, 2GB RAM, 8GB disk + +**Features**: +- Web-based Docker Compose management +- Manages Homelab Dashboard stack +- Real-time container logs and stats + +**Startup**: +```bash +pct exec 127 -- systemctl status docker +pct exec 127 -- systemctl restart dockge +``` + +**Health Check**: +```bash +curl -I http://10.0.10.27:5001 +``` + +**Status**: โœ… Operational + +--- + +### Media Automation Stack (Arr Services) + +**Purpose**: Automated TV show, movie, and subtitle management with torrent downloading + +**Architecture**: Docker Compose stack on CT 127 (Dockge), HTTPS termination via Caddy internal proxy + +#### Sonarr (TV Shows) + +**Service Details**: +- **Host**: main-pve (CT 127, Docker container) +- **IP Address**: 10.0.10.27 +- **Port**: 8989 (HTTP backend) +- **HTTPS URL**: https://sonarr.nianticbooks.home +- **Purpose**: TV show monitoring, RSS feed monitoring, automatic downloads + +**Features**: +- Episode calendar and upcoming releases +- Automatic quality upgrades +- Integration with Prowlarr for indexers +- Integration with Deluge for downloads +- Library management and renaming + +#### Radarr (Movies) + +**Service Details**: +- **Host**: main-pve (CT 127, Docker container) +- **IP Address**: 10.0.10.27 +- **Port**: 7878 (HTTP backend) +- **HTTPS URL**: https://radarr.nianticbooks.home +- **Purpose**: Movie monitoring, automatic downloads, library management + +**Features**: +- Movie discovery and recommendations +- Automatic quality upgrades +- Integration with Prowlarr for indexers +- Integration with Deluge for downloads +- Library management and renaming + +#### Prowlarr (Indexer Manager) + +**Service Details**: +- **Host**: main-pve (CT 127, Docker container) +- **IP Address**: 10.0.10.27 +- **Port**: 9696 (HTTP backend) +- **HTTPS URL**: https://prowlarr.nianticbooks.home +- **Purpose**: Centralized indexer management for Sonarr/Radarr + +**Features**: +- Single configuration point for all indexers +- Automatic sync to Sonarr/Radarr +- Indexer statistics and testing + +#### Bazarr (Subtitles) + +**Service Details**: +- **Host**: main-pve (CT 127, Docker container) +- **IP Address**: 10.0.10.27 +- **Port**: 6767 (HTTP backend) +- **HTTPS URL**: https://bazarr.nianticbooks.home +- **Purpose**: Automatic subtitle download for TV shows and movies + +**Features**: +- Integration with Sonarr/Radarr +- Multiple subtitle provider support +- Automatic language selection +- Missing subtitle detection + +#### Deluge (BitTorrent Client) + +**Service Details**: +- **Host**: main-pve (CT 127, Docker container) +- **IP Address**: 10.0.10.27 +- **Port**: 8112 (HTTP backend) +- **HTTPS URL**: https://deluge.nianticbooks.home +- **Purpose**: BitTorrent download client for media files + +**Features**: +- Web UI for torrent management +- Integration with Sonarr/Radarr +- Automatic category-based sorting +- Seeding management + +#### Calibre-Web (eBook Library) + +**Service Details**: +- **Host**: main-pve (CT 127, Docker container) +- **IP Address**: 10.0.10.27 +- **Port**: 8083 (HTTP backend) +- **HTTPS URL**: https://calibre.nianticbooks.home +- **Purpose**: eBook library management and reading + +**Features**: +- Web-based eBook reader +- eBook format conversion +- Library organization and metadata management +- Send-to-Kindle integration + +**Storage Configuration**: + +All media services store data on OpenMediaVault (10.0.10.5) via NFS mounts: +- `/media/tv` - Sonarr TV library +- `/media/movies` - Radarr movie library +- `/media/downloads` - Deluge download directory +- `/media/books` - Calibre library + +**Startup**: +```bash +# Access Dockge UI +http://10.0.10.27:5001 + +# Or via SSH to CT 127 +pct exec 127 -- docker ps | grep -E "(sonarr|radarr|prowlarr|bazarr|deluge|calibre)" +pct exec 127 -- docker restart +``` + +**Health Check**: +```bash +# Check all media services +for service in sonarr radarr prowlarr bazarr deluge calibre; do + echo "Checking $service..." + curl -I https://$service.nianticbooks.home 2>/dev/null | head -1 +done +``` + +**Status**: โœ… Operational (deployed 2026-01-25) + +--- + +### Caddy Internal Reverse Proxy + +**Purpose**: HTTPS termination and reverse proxy for internal homelab services + +**Service Details**: +- **Host**: main-pve (CT 127, Docker container) +- **Container Name**: caddy-internal +- **IP Address**: 10.0.10.27 +- **Port**: 443 (HTTPS) +- **Configuration**: `/opt/caddy-internal/Caddyfile` +- **Certificate Authority**: Caddy Local Authority - 2026 ECC Root + +**Services Proxied**: +- Sonarr, Radarr, Prowlarr, Bazarr, Deluge, Calibre-Web +- Vikunja, Dockge + +**Startup**: +```bash +pct exec 127 -- docker start caddy-internal +pct exec 127 -- docker restart caddy-internal +``` + +**Health Check**: +```bash +pct exec 127 -- docker ps | grep caddy-internal +pct exec 127 -- docker logs caddy-internal +``` + +**Status**: โœ… Operational (deployed 2026-01-25) + +**Certificate Installation**: + +See `CA-DEPLOYMENT-SUMMARY.md` for client certificate installation instructions. + +--- + +### RustDesk (Self-Hosted Remote Desktop) + +**Purpose**: Secure remote desktop access with self-hosted infrastructure + +**Service Details**: +- **ID Server (hbbs)**: LXC 123 on main-pve + - **IP**: 10.0.10.23 + - **Version**: 1.1.14 + - **Ports**: 21115 (NAT test), 21116 (ID/Rendezvous), 21118 (TCP punch) +- **Relay Server (hbbr)**: VPS (66.63.182.168) + - **Version**: 1.1.14 + - **Port**: 21117 (Relay service) +- **Public Key**: `sfYuCTMHxrA22kukomb/RAKYyUgr8iaMfm/U4CFLfL0=` + +**Architecture**: +``` +Internet โ†’ VPS Relay (hbbr) + โ†“ + WireGuard Tunnel + โ†“ + Home Lab ID Server (hbbs) + โ†“ + RustDesk Clients (P2P when possible) +``` + +**Client Configuration**: +- **ID Server**: `66.63.182.168` +- **Relay Server**: `66.63.182.168` (auto-configured) +- **Key**: `sfYuCTMHxrA22kukomb/RAKYyUgr8iaMfm/U4CFLfL0=` + +**Startup**: +```bash +# ID Server (home lab) +ssh root@10.0.10.3 +pct exec 123 -- systemctl status rustdesk-hbbs +pct exec 123 -- systemctl restart rustdesk-hbbs + +# Relay Server (VPS) +ssh 66.63.182.168 +sudo systemctl status rustdesk-hbbr +sudo systemctl restart rustdesk-hbbr +``` + +**Health Check**: +```bash +# ID Server ports +nc -zv 10.0.10.23 21115 # NAT test +nc -zv 10.0.10.23 21116 # ID/Rendezvous + +# Relay Server port +nc -zv 66.63.182.168 21117 +``` + +**Logs**: +```bash +# ID Server +ssh root@10.0.10.3 'pct exec 123 -- journalctl -u rustdesk-hbbs -f' + +# Relay Server +ssh 66.63.182.168 'sudo journalctl -u rustdesk-hbbr -f' +``` + +**Status**: โœ… Operational - Deployed 2025-12-25 + +**See Also**: guides/RUSTDESK-DEPLOYMENT-COMPLETE.md for complete setup guide + +--- + +### AD5M 3D Printer (Prusa) + +**Purpose**: 3D printing + +**Service Details**: +- **IP Address**: 10.0.10.30 +- **MAC**: 88:a9:a7:99:c3:64 +- **DNS**: AD5M.nianticbooks.home +- **Web Interface**: http://10.0.10.30 +- **Public Domain**: https://ad5m.nianticbooks.com โœ… Working + +**Health Check**: +```bash +ping 10.0.10.30 +curl -I http://10.0.10.30 +``` + +**Status**: โœ… Operational + +--- + +### Minecraft Forge Server + +**Purpose**: Game server for Cisco's Fantasy Medieval RPG Ultimate modpack + +**Service Details**: +- **Host**: main-pve (CT 130) +- **IP Address**: 10.0.10.41 +- **Game Port**: 25565 (TCP/UDP) +- **Status Page Port**: 8080 (HTTP) +- **Public Domain**: cfmu.deadeyeg4ming.vip:25565 (game) +- **Status Page**: https://cfmu.deadeyeg4ming.vip (web) +- **Resources**: 8 vCPUs, 20GB RAM, 100GB disk + +**Configuration**: +- Minecraft Version: 1.20.1 +- Forge Version: 47.3.0 +- Modpack: Cisco's Fantasy Medieval RPG Ultimate +- Max Players: 20 +- View Distance: 12 +- Difficulty: Normal +- Game Mode: Survival + +**Startup**: +```bash +# Via Proxmox +ssh root@10.0.10.3 +pct exec 130 -- systemctl status minecraft-forge.service +pct exec 130 -- systemctl start minecraft-forge.service +pct exec 130 -- systemctl stop minecraft-forge.service # Graceful 30-sec countdown + +# Access server console +pct exec 130 -- screen -r minecraft +# Ctrl+A, D to detach +``` + +**Health Check**: +```bash +# Check port +nc -zv 10.0.10.41 25565 + +# Check service status +ssh root@10.0.10.3 'pct exec 130 -- systemctl status minecraft-forge.service' + +# Check status page +curl -I http://10.0.10.41:8080/ +``` + +**Backup**: +- Automated daily backup at 3:00 AM UTC +- Script: `/opt/minecraft/scripts/backup-minecraft.sh` +- Location: /mnt/omv-backups/minecraft-YYYYMMDD_HHMMSS/ +- Retention: 14 days +- Components backed up: + - World data (overworld, nether, end) + - Server configs and mods + - Player data + +**Monitoring**: +- Health check every 5 minutes via systemd timer +- Auto-restart on crash +- Discord webhook notifications (optional) + +**Logs**: +```bash +# Server logs +ssh root@10.0.10.3 'pct exec 130 -- tail -f /opt/minecraft/server/logs/latest.log' + +# Service logs +ssh root@10.0.10.3 'pct exec 130 -- journalctl -u minecraft-forge.service -f' + +# Backup logs +ssh root@10.0.10.3 'pct exec 130 -- journalctl -u minecraft-backup.service' + +# Health check logs +ssh root@10.0.10.3 'pct exec 130 -- journalctl -u minecraft-health.service' +``` + +**Status**: โœ… Operational - Deployed 2026-01-10 + +**Architecture**: +``` +Players โ†’ cfmu.deadeyeg4ming.vip:25565 + โ†’ Gaming VPS (51.222.12.162) iptables forward + โ†’ WireGuard tunnel (10.0.9.0/24) + โ†’ Minecraft Server (10.0.10.41:25565) +``` + +**See Also**: mc_server/ directory for deployment scripts and configuration files + +--- + +### OpenClaw Gateway (AI Agent Coordinator) + +**Purpose**: Multi-agent AI coordination platform with voice integration, morning briefings, and proactive automation + +**Service Details**: +- **Host**: main-pve (CT 130) +- **IP Address**: 10.0.10.28 +- **Port**: 18789 (WebSocket/HTTP) +- **Version**: Latest (to be installed) +- **Resources**: 2 vCPUs, 4GB RAM, 16GB disk + +**Desktop Client**: +- **Device**: Fred's iMac (10.0.10.11 Ethernet / 10.0.10.144 Wi-Fi) +- **Hardware**: Late 2013 iMac, 3.2GHz i5, 24GB RAM +- **OS**: macOS Sequoia (via OpenCore) +- **Features**: Voice input/output, morning briefings, system integration +- **Network**: Dual-interface (Ethernet configured but cable not connected, currently on Wi-Fi) + +**Automated Workflows**: + +1. **Morning Brief (8:00 AM Daily)** + - Local weather forecast + - Trending YouTube videos (filtered by interests) + - Daily todo list and task recommendations + - Trending news stories (filtered by interests) + - Productivity recommendations + +2. **Proactive Coder (11:00 PM Nightly)** + - Overnight development work on business improvements + - Creates Pull Requests for review (no direct commits) + - Infrastructure monitoring and optimization + - Workflow automation improvements + +3. **Second Brain (NextJS App)** + - Obsidian/Linear-style document viewer + - Auto-generated concept exploration documents + - Daily journal entries + - Knowledge base from conversations + +4. **Afternoon Research Report (Daily)** + - Deep dives on topics of interest + - Process improvement recommendations + - Productivity workflow suggestions + +**Startup**: +```bash +# Gateway service +ssh root@10.0.10.3 +pct exec 130 -- systemctl status openclaw-gateway +pct exec 130 -- systemctl start openclaw-gateway +pct exec 130 -- systemctl restart openclaw-gateway + +# Desktop client +# Launch OpenClaw app from macOS Applications folder +``` + +**Health Check**: +```bash +# Gateway accessibility +curl -I http://10.0.10.28:18789 + +# Service status +ssh root@10.0.10.3 'pct exec 130 -- openclaw status' + +# Check sessions +ssh root@10.0.10.3 'pct exec 130 -- openclaw sessions' +``` + +**Logs**: +```bash +# Gateway logs +ssh root@10.0.10.3 'pct exec 130 -- journalctl -u openclaw-gateway -f' + +# OpenClaw dashboard +http://10.0.10.28:18789/ +``` + +**Configuration**: +- Gateway config: `/root/.openclaw/` on CT 130 +- Node.js: โ‰ฅ22.12.0 LTS (security requirements) +- Authentication: Token-based for LAN access +- Network: Internal only (no public exposure) + +**Planned Integrations**: +- Home Assistant (10.0.10.24) - Voice control for smart home +- n8n (10.0.10.22) - Workflow automation webhooks +- Calendar - Morning briefing with daily schedule +- Weather API - Local forecasts +- YouTube Data API - Trending videos by interest +- News APIs - Filtered trending stories + +**Status**: โœ… Running - Gateway operational, desktop client ready for connection + +**Current Configuration**: + +- **Gateway PID**: Running (check with `pgrep -f 'openclaw gateway'`) +- **Auth Token**: Configured for LAN access +- **Commands Enabled**: bash (native commands enabled) +- **Model**: claude-sonnet-4-5 +- **Hooks**: boot-md, command-logger, session-memory +- **User Profile**: `/root/USER.md` with Fred's preferences + - Timezone: America/Chicago (CST/CDT) + - Location: ZIP 62551 + - Interests: Tech/AI, Homelab, 3D Printing + - Todo Integration: Apple Reminders (iPhone/iMac) + +**See Also**: +- [OPENCLAW-SETUP.md](OPENCLAW-SETUP.md) for detailed setup guide +- GitHub: https://github.com/openclaw/openclaw +- Docs: https://docs.openclaw.ai + +--- + +## Service Dependencies + +### Dependency Map + +``` +Internet + โ””โ”€> Gaming VPS (51.222.12.162) + โ”œโ”€> WireGuard Server (10.0.9.1) + โ”‚ โ”œโ”€> UCG Ultra WireGuard Client (10.0.9.2) + โ”‚ โ””โ”€> VPS Proxy Client (10.0.9.3) + โ”‚ โ””โ”€> Home Lab Network (10.0.10.0/24) + โ”‚ โ”œโ”€> Proxmox Cluster + โ”‚ โ”œโ”€> PostgreSQL (shared DB) + โ”‚ โ”œโ”€> Authentik SSO + โ”‚ โ”œโ”€> n8n + โ”‚ โ”œโ”€> Home Assistant + โ”‚ โ””โ”€> Other services + โ”‚ + โ””โ”€> Caddy Reverse Proxy + โ””โ”€> Routes to services via tunnel: + โ”œโ”€> freddesk.nianticbooks.com โ†’ 10.0.10.3:8006 + โ”œโ”€> ad5m.nianticbooks.com โ†’ 10.0.10.30:80 + โ””โ”€> bob.nianticbooks.com โ†’ 10.0.10.24:8123 +``` + +### Critical Service Dependencies + +| Service | Depends On | Impact if Dependency Fails | +|---------|------------|----------------------------| +| Caddy | WireGuard tunnel, DNS | All public services unavailable | +| WireGuard | UCG Ultra, VPS connectivity | Public services unavailable | +| Authentik | PostgreSQL, network | SSO login fails (local admin still works) | +| n8n | PostgreSQL, network | Workflows stop | +| Home Assistant | Network, OMV (optional) | Smart home control unavailable | +| All Services | Proxmox, network | Service unavailable | +| Proxmox VMs | OMV (for backups) | Backups fail (services continue) | + +--- + +## Monitoring & Health Checks + +### Service Health Check Matrix + +| Service | Check Method | Expected Response | Status | +|---------|--------------|-------------------|--------| +| WireGuard | `sudo wg show` on VPS | Peer connected, handshake active | โœ… Operational | +| Caddy | `curl -I https://freddesk.nianticbooks.com` | HTTP 200 or 501 | โœ… Operational | +| Proxmox | `curl -k -I https://10.0.10.3:8006` | HTTP 501 (HEAD not supported) | โœ… Operational | +| PostgreSQL | `ping 10.0.10.20` | Reply | โœ… Operational | +| Authentik | `curl -I http://10.0.10.21:9000` | HTTP 302 redirect to login | โœ… Operational | +| n8n | `curl -I http://10.0.10.22:5678` | HTTP 200 | โœ… Operational | +| Home Assistant | `curl -k -I https://10.0.10.24:8123` | HTTP 405 (HEAD not allowed) | โœ… Operational | +| Dockge | `curl -I http://10.0.10.27:5001` | HTTP 200 | โœ… Operational | +| RustDesk ID | `nc -zv 10.0.10.23 21116` | Connection succeeds | โœ… Operational | +| RustDesk Relay | `nc -zv 66.63.182.168 21117` | Connection succeeds | โœ… Operational | +| 3D Printer | `curl -I http://10.0.10.30` | HTTP 200 | โœ… Operational | + +### Automated Health Monitoring + +**Current Status**: Manual checks + +**Planned**: Prometheus + Grafana deployment (10.0.10.25) + +See [MONITORING.md](MONITORING.md) for detailed monitoring setup when ready. + +--- + +## Notes & TODO + +### Working Services +All critical infrastructure services are operational and verified. + +### Known Issues +No known issues - all critical services operational and accessible. + +### Planned Services +See INFRASTRUCTURE-TODO.md for: +- Additional monitoring configuration (Prometheus targets, dashboards) + +--- + +**Last Verified**: 2025-12-29 03:10 UTC +**Verified By**: Fred (with Claude Code) +**Next Review**: Quarterly or after major changes + +**Recent Changes** (2025-12-29): +- โœ… Fixed Home Assistant public domain (bob.nianticbooks.com) +- โœ… Discovered Prometheus + Grafana already deployed at 10.0.10.25 +- โœ… Discovered RustDesk already deployed (ID server 10.0.10.23, relay on VPS) +- โœ… Discovered additional public domains: auth.nianticbooks.com, bible.nianticbooks.com +- โœ… All 5 public domains now operational +- โœ… Updated Home Assistant trusted_proxies to include VPS WireGuard IP (10.0.9.3) +- โœ… Added comprehensive RustDesk documentation (client config, public key, health checks) diff --git a/infrastructure/SSH pub key ssh ed25519.txt b/infrastructure/SSH pub key ssh ed25519.txt new file mode 100644 index 0000000..ddf4b44 --- /dev/null +++ b/infrastructure/SSH pub key ssh ed25519.txt @@ -0,0 +1,3 @@ +SSH pub key +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIl9GHvvTkyHLW/XHTOaN/1ylqzzj7lQJYpHEDRHLPW0 surface-go-assistant +H \ No newline at end of file diff --git a/infrastructure/SSH-SETUP-GUIDE.md b/infrastructure/SSH-SETUP-GUIDE.md new file mode 100644 index 0000000..35b28d9 --- /dev/null +++ b/infrastructure/SSH-SETUP-GUIDE.md @@ -0,0 +1,400 @@ +# SSH Setup Guide - Homelab Infrastructure + +Complete guide for setting up SSH connections between Windows machines in the homelab. + +## Overview + +This guide documents the SSH configuration between: +- **HOMELAB-COMMAND** (10.0.10.10) - Windows client/workstation +- **M6800** - Windows SSH server + +## Architecture + +``` +HOMELAB-COMMAND (10.0.10.10) + โ†“ SSH (Port 22) +M6800 (Windows OpenSSH Server) + - Projects in C:\Users\Fred\projects + - Infrastructure scripts + - Development environment +``` + +## Prerequisites + +- Windows 10/11 on both machines +- Both machines on same network (10.0.10.0/24) +- Administrator access on M6800 for SSH server setup + +## Setup Steps + +### 1. Install OpenSSH Server on M6800 + +```powershell +# Check if OpenSSH Server is installed +Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH.Server*' + +# Install if not present +Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 + +# Start the service +Start-Service sshd + +# Enable automatic startup +Set-Service -Name sshd -StartupType 'Automatic' + +# Verify service is running +Get-Service sshd +``` + +### 2. Configure Windows Firewall on M6800 + +```powershell +# Allow SSH through Windows Firewall +New-NetFirewallRule -Name sshd -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22 + +# Or use the automated script +# C:\Users\Fred\projects\infrastructure\scripts\enable-ssh-firewall.ps1 +``` + +### 3. Generate SSH Keys on HOMELAB-COMMAND + +```powershell +# Generate ED25519 key pair (recommended for better security) +ssh-keygen -t ed25519 -f $env:USERPROFILE\.ssh\id_ed25519 -C "homelab-command-to-m6800" + +# Or generate RSA key (if ED25519 not supported) +ssh-keygen -t rsa -b 4096 -f $env:USERPROFILE\.ssh\id_rsa -C "homelab-command-to-m6800" + +# Keys will be created in: +# - Private key: C:\Users\Fred\.ssh\id_ed25519 +# - Public key: C:\Users\Fred\.ssh\id_ed25519.pub +``` + +### 4. Copy Public Key to M6800 + +**Manual Method:** + +```powershell +# On HOMELAB-COMMAND - Display your public key +Get-Content $env:USERPROFILE\.ssh\id_ed25519.pub + +# On M6800 - Create .ssh directory if needed +$sshDir = "$env:USERPROFILE\.ssh" +if (!(Test-Path $sshDir)) { + New-Item -Path $sshDir -ItemType Directory -Force +} + +# Create authorized_keys file and paste the public key +# Note: For administrators, use administrators_authorized_keys instead +$authKeysFile = "$env:ProgramData\ssh\administrators_authorized_keys" +Set-Content -Path $authKeysFile -Value "YOUR_PUBLIC_KEY_HERE" + +# Set correct permissions (critical!) +icacls.exe "$authKeysFile" /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F" +``` + +**Automated Method (if SSH password auth is enabled):** + +```powershell +# From HOMELAB-COMMAND +type $env:USERPROFILE\.ssh\id_ed25519.pub | ssh fred@M6800 "mkdir -p C:\ProgramData\ssh; cat >> C:\ProgramData\ssh\administrators_authorized_keys" + +# Fix permissions on M6800 +ssh fred@M6800 "icacls.exe C:\ProgramData\ssh\administrators_authorized_keys /inheritance:r /grant Administrators:F /grant SYSTEM:F" +``` + +### 5. Configure SSH Client on HOMELAB-COMMAND + +Create or edit `C:\Users\Fred\.ssh\config`: + +``` +Host m6800 + HostName M6800 + User Fred + IdentityFile C:\Users\Fred\.ssh\id_ed25519 + ServerAliveInterval 60 + ServerAliveCountMax 3 + +Host homelab-command + HostName 10.0.10.10 + User Fred + IdentityFile C:\Users\Fred\.ssh\id_ed25519 +``` + +### 6. Test the Connection + +```powershell +# Test SSH connection +ssh m6800 + +# Test with verbose output (for troubleshooting) +ssh -v m6800 + +# Test specific command +ssh m6800 "hostname" +``` + +## Troubleshooting + +### Connection Refused + +**Symptoms:** +``` +ssh: connect to host M6800 port 22: Connection refused +``` + +**Solutions:** +1. Check SSH service is running on M6800: + ```powershell + Get-Service sshd + Start-Service sshd # If stopped + ``` + +2. Verify firewall rule exists: + ```powershell + Get-NetFirewallRule -Name sshd + ``` + +3. Test network connectivity: + ```powershell + Test-NetConnection -ComputerName M6800 -Port 22 + ``` + +### Permission Denied (publickey) + +**Symptoms:** +``` +Permission denied (publickey,keyboard-interactive) +``` + +**Solutions:** +1. Verify public key is in correct location: + - For administrators: `C:\ProgramData\ssh\administrators_authorized_keys` + - For regular users: `C:\Users\Fred\.ssh\authorized_keys` + +2. Check file permissions on M6800: + ```powershell + icacls C:\ProgramData\ssh\administrators_authorized_keys + ``` + Should show only Administrators and SYSTEM with Full control. + +3. Verify SSH key is being offered: + ```powershell + ssh -v m6800 2>&1 | Select-String "Offering" + ``` + +4. Restart SSH service after permission changes: + ```powershell + Restart-Service sshd + ``` + +### Name Resolution Issues + +**Symptoms:** +``` +Could not resolve hostname M6800 +``` + +**Solutions:** +1. Use IP address instead of hostname in SSH config +2. Add entry to hosts file: + ```powershell + Add-Content -Path C:\Windows\System32\drivers\etc\hosts -Value "10.0.10.XX M6800" + ``` +3. Configure static DNS entry in router/DHCP server + +### Slow Connection/Hangs + +**Solutions:** +1. Add to SSH config to disable GSSAPI authentication: + ``` + GSSAPIAuthentication no + ``` + +2. Increase verbosity to identify hang point: + ```powershell + ssh -vvv m6800 + ``` + +## Security Best Practices + +### 1. Disable Password Authentication (After Key Setup Works) + +On M6800, edit `C:\ProgramData\ssh\sshd_config`: + +``` +PasswordAuthentication no +PubkeyAuthentication yes +``` + +Restart SSH service: +```powershell +Restart-Service sshd +``` + +### 2. Change Default SSH Port (Optional) + +Edit `C:\ProgramData\ssh\sshd_config`: +``` +Port 2222 # Or any port above 1024 +``` + +Update firewall rule: +```powershell +New-NetFirewallRule -Name sshd-custom -DisplayName 'OpenSSH Server (Custom Port)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 2222 +``` + +### 3. Restrict SSH Access by IP + +```powershell +# Allow SSH only from specific IP +New-NetFirewallRule -Name sshd-restricted -DisplayName 'OpenSSH Server (Restricted)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22 -RemoteAddress 10.0.10.10 +``` + +### 4. Key Management + +- Use passphrase-protected keys for sensitive environments +- Rotate keys periodically (every 6-12 months) +- Remove old keys from authorized_keys file +- Keep private keys secure - never share them + +### 5. Monitoring + +Enable SSH logging on M6800: + +```powershell +# View SSH logs +Get-WinEvent -LogName "OpenSSH/Operational" | Select-Object -First 20 + +# Monitor failed login attempts +Get-WinEvent -LogName "OpenSSH/Operational" | Where-Object {$_.Message -like "*failed*"} +``` + +## Automated Scripts + +### enable-ssh-firewall.ps1 + +Location: `C:\Users\Fred\projects\infrastructure\scripts\enable-ssh-firewall.ps1` + +Automatically configures firewall rules for SSH server. + +### setup-ssh-server.ps1 + +Automated script for complete SSH server setup on Windows: + +```powershell +# Install OpenSSH Server +Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 + +# Configure service +Start-Service sshd +Set-Service -Name sshd -StartupType 'Automatic' + +# Configure firewall +New-NetFirewallRule -Name sshd -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22 + +# Create admin authorized_keys file +$authKeysFile = "$env:ProgramData\ssh\administrators_authorized_keys" +New-Item -Path $authKeysFile -ItemType File -Force +icacls.exe "$authKeysFile" /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F" +``` + +### test-homelab-ssh.sh + +Location: `C:\Users\Fred\projects\infrastructure\scripts\test-homelab-ssh.sh` + +Tests SSH connectivity between homelab machines. + +## Common Use Cases + +### 1. Remote File Transfer (SCP) + +```powershell +# Copy file to M6800 +scp localfile.txt m6800:C:/Users/Fred/ + +# Copy file from M6800 +scp m6800:C:/Users/Fred/remotefile.txt ./ + +# Copy directory recursively +scp -r localdir/ m6800:C:/Users/Fred/remotedir/ +``` + +### 2. Remote Command Execution + +```powershell +# Single command +ssh m6800 "powershell -Command Get-Process" + +# Multiple commands +ssh m6800 "powershell -Command 'Get-Date; hostname; Get-Service sshd'" + +# Run script +ssh m6800 "powershell -ExecutionPolicy Bypass -File C:/scripts/script.ps1" +``` + +### 3. Port Forwarding + +```powershell +# Local port forwarding - Access M6800 service on local port 8080 +ssh -L 8080:localhost:80 m6800 + +# Remote port forwarding - Expose local service to M6800 +ssh -R 9090:localhost:8080 m6800 + +# Dynamic port forwarding (SOCKS proxy) +ssh -D 1080 m6800 +``` + +### 4. SSH Tunneling for Home Assistant + +```powershell +# Forward Home Assistant port through SSH tunnel +ssh -L 8123:localhost:8123 m6800 + +# Access in browser: http://localhost:8123 +``` + +## Integration with Claude Code + +Claude Code can use SSH to access remote development environments: + +```bash +# From Claude Code on HOMELAB-COMMAND +# Access M6800 projects directory +ssh m6800 "cd C:/Users/Fred/projects && dir" + +# Edit files remotely +ssh m6800 "powershell -Command 'Get-Content C:/Users/Fred/projects/file.txt'" +``` + +## Maintenance Tasks + +### Weekly +- Review SSH logs for failed attempts +- Check authorized_keys file for unauthorized entries + +### Monthly +- Test SSH connectivity from all client machines +- Verify firewall rules are correct +- Update OpenSSH if new version available + +### Quarterly +- Review and remove old SSH keys +- Audit SSH configuration for security best practices +- Test backup SSH access methods + +## References + +- [Microsoft OpenSSH Documentation](https://learn.microsoft.com/en-us/windows-server/administration/openssh/openssh_overview) +- [OpenSSH Manual Pages](https://www.openssh.com/manual.html) +- Infrastructure scripts: `C:\Users\Fred\projects\infrastructure\scripts\` + +## Recent Work Log + +### 2025-12-13 +- Configured SSH server on M6800 +- Set up SSH key authentication from HOMELAB-COMMAND +- Created firewall rules for SSH access +- Tested bidirectional connectivity +- Documented troubleshooting steps for permission issues diff --git a/infrastructure/Screenshot 2025-11-17 225014.png b/infrastructure/Screenshot 2025-11-17 225014.png new file mode 100644 index 0000000..f98f5c9 Binary files /dev/null and b/infrastructure/Screenshot 2025-11-17 225014.png differ diff --git a/infrastructure/Screenshot 2025-11-17 230122.png b/infrastructure/Screenshot 2025-11-17 230122.png new file mode 100644 index 0000000..5f513b9 Binary files /dev/null and b/infrastructure/Screenshot 2025-11-17 230122.png differ diff --git a/infrastructure/Screenshot 2025-12-27 092411.png b/infrastructure/Screenshot 2025-12-27 092411.png new file mode 100644 index 0000000..5b23f46 Binary files /dev/null and b/infrastructure/Screenshot 2025-12-27 092411.png differ diff --git a/infrastructure/authentik-homeassistant-blueprint.yaml b/infrastructure/authentik-homeassistant-blueprint.yaml new file mode 100644 index 0000000..e0f2df0 --- /dev/null +++ b/infrastructure/authentik-homeassistant-blueprint.yaml @@ -0,0 +1,28 @@ +version: 1 +metadata: + name: Home Assistant OAuth2 Integration +entries: + - model: authentik_providers_oauth2.oauth2provider + id: homeassistant-provider + identifiers: + name: Home Assistant + attrs: + authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]] + invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]] + client_type: confidential + client_id: !Format [homeassistant-%s, !Env RANDOM_ID] + client_secret: !Format [%s, !Env RANDOM_SECRET] + redirect_uris: | + https://bob.nianticbooks.com/auth/external/callback + signing_key: !Find [authentik_crypto.certificatekeypair, [name, authentik Self-signed Certificate]] + sub_mode: hashed_user_id + include_claims_in_id_token: true + + - model: authentik_core.application + id: homeassistant-app + identifiers: + slug: home-assistant + attrs: + name: Home Assistant + provider: !KeyOf homeassistant-provider + launch_url: https://bob.nianticbooks.com diff --git a/infrastructure/claude-shared/index.html b/infrastructure/claude-shared/index.html new file mode 100644 index 0000000..941ffe6 --- /dev/null +++ b/infrastructure/claude-shared/index.html @@ -0,0 +1,23 @@ + + + + +Directory listing for /infrastructure/claude-shared/ + + +

Directory listing for /infrastructure/claude-shared/

+
+ +
+ + diff --git a/infrastructure/dhcp-export-all-2025-11-14T22-55-18.871Z.csv b/infrastructure/dhcp-export-all-2025-11-14T22-55-18.871Z.csv new file mode 100644 index 0000000..b1c71c0 --- /dev/null +++ b/infrastructure/dhcp-export-all-2025-11-14T22-55-18.871Z.csv @@ -0,0 +1,50 @@ +"MAC Address","IP Address",Hostname,"Local DNS Record","Lease Type",Name,"Expiration Time" +"02:f5:e9:54:36:28","10.0.10.194",homeassistant,,Dynamic,"homeassistant 36:28","2025-11-15T08:19:09.000-06:00" +"10:d5:61:3b:ce:32","10.0.10.76",wlan0,,Dynamic,"wlan0 ce:32","2025-11-15T05:19:04.000-06:00" +"32:5a:63:c9:e9:d7","10.0.10.129",Watch,,Dynamic,"Watch e9:d7","2025-11-15T09:17:22.000-06:00" +"44:61:32:90:e0:a3","10.0.10.102","My-ecobee",,Dynamic,"My-ecobee e0:a3","2025-11-15T13:50:38.000-06:00" +"62:03:ac:1f:89:40","10.0.10.202",iPhone,,Dynamic,"iPhone 89:40","2025-11-15T16:18:00.000-06:00" +"64:5d:86:15:de:20","10.0.10.157",KobePC,,Dynamic,"KobePC de:20","2025-11-15T16:02:18.000-06:00" +"64:da:ed:1c:b5:6d","10.0.10.227",eero,,Dynamic,"eero b5:6d","2025-11-15T15:37:17.000-06:00" +"64:da:ed:29:12:ad","10.0.10.101",eero,,Dynamic,"eero 12:ad","2025-11-15T06:48:00.000-06:00" +"64:da:ed:29:2e:8d","10.0.10.216",eero,,Dynamic,"eero 2e:8d","2025-11-15T16:01:22.000-06:00" +"68:57:2d:b4:dd:25","10.0.10.170","TY_WR",,Dynamic,"TY_WR dd:25","2025-11-15T09:12:44.000-06:00" +"6c:c8:40:05:5c:68","10.0.10.173","esphome-web-055c68",,Dynamic,"esphome-web-055c68 5c:68","2025-11-15T08:04:29.000-06:00" +"6e:7a:50:fa:de:94","10.0.10.238",,,Dynamic,"Docker de:94","2025-11-15T07:57:17.000-06:00" +"70:66:2a:65:36:bc","10.0.10.174",,,Dynamic,"Sony PlayStation 5 36:bc","2025-11-15T09:43:22.000-06:00" +"70:89:76:ba:0f:d4","10.0.10.81",wlan0,,Dynamic,"wlan0 0f:d4","2025-11-15T10:39:16.000-06:00" +"70:89:76:bc:f4:a4","10.0.10.151",wlan0,,Dynamic,"wlan0 f4:a4","2025-11-15T10:32:53.000-06:00" +"7c:f6:66:44:68:f6","10.0.10.201",wlan0,,Dynamic,"wlan0 68:f6","2025-11-15T14:21:51.000-06:00" +"7c:f6:66:45:44:44","10.0.10.160",wlan0,,Dynamic,"wlan0 44:44","2025-11-15T16:18:47.000-06:00" +"7e:e4:a4:b8:c2:f3","10.0.10.55",iPhone,,Dynamic,"iPhone c2:f3","2025-11-15T15:55:10.000-06:00" +"80:00:6e:f2:13:52","10.0.10.105","Freds-Mac-Pro",,Dynamic,"Jill's MacPro","2025-11-15T06:52:49.000-06:00" +"82:cc:cf:ec:5e:da","10.0.10.171",iPad,,Dynamic,"iPad 5e:da","2025-11-15T06:51:23.000-06:00" +"84:d6:c5:4a:70:32","10.0.10.62",,,Dynamic,"Solaredge SE7K 70:32","2025-11-15T16:47:36.000-06:00" +"84:f3:eb:c1:dd:aa","10.0.10.90","ESP_C1DDAA",,Dynamic,"ESP_C1DDAA dd:aa","2025-11-15T14:50:31.000-06:00" +"88:a9:a7:99:c3:64","10.0.10.189",AD5M,"AD5M.nianticbooks.home",Fixed,ad5m,"1969-12-31T18:00:00.000-06:00" +"88:e7:12:04:fc:06","10.0.10.195","MICRW_88_E7_12_04_FC_06",,Dynamic,"MICRW_88_E7_12_04_FC_06 fc:06","2025-11-15T05:58:46.000-06:00" +"90:de:80:80:e7:04","10.0.10.92","HOMELAB-COMMAND","HOMELAB-COMMAND.nianticbooks.home",Fixed,"Dad's PC","1969-12-31T18:00:00.000-06:00" +"a0:ad:9f:30:8c:af","10.0.10.213","Kevin-PC",,Dynamic,"Kevin-PC 8c:af","2025-11-15T12:23:50.000-06:00" +"a8:2c:3e:bc:e2:bf","10.0.10.235",,,Dynamic,"Jill's Monitor","2025-11-15T16:27:13.000-06:00" +"ac:41:6a:69:3a:8e","10.0.10.154",,,Dynamic,"Blink XT Security Camera 3a:8e","2025-11-15T08:10:01.000-06:00" +"ac:fd:ce:e6:9f:d8","10.0.10.241","Fred-M6800",,Dynamic,"Fred-M6800 9f:d8","2025-11-15T16:26:57.000-06:00" +"b4:b5:2f:ea:8c:30","10.0.10.53",ilob4b52fea8c30,"ilo.nianticbooks.home",Fixed,"HP iLO","1969-12-31T18:00:00.000-06:00" +"b8:09:8a:ca:6c:53","10.0.10.144","Freds-iMac",,Dynamic,"Freds-iMac 6c:53","2025-11-15T16:26:40.000-06:00" +"bc:24:11:0f:78:84","10.0.10.79","pve-scripts-local",,Dynamic,"pve-scripts-local 78:84","2025-11-15T14:29:45.000-06:00" +"bc:24:11:4a:42:07","10.0.10.104",dockge,,Dynamic,"dockge 42:07","2025-11-15T12:41:26.000-06:00" +"bc:24:11:57:dd:94","10.0.10.112",authelia,,Dynamic,"authelia dd:94","2025-11-15T14:54:51.000-06:00" +"bc:24:11:8f:eb:eb","10.0.10.113",esphome,,Dynamic,"esphome eb:eb","2025-11-15T13:17:30.000-06:00" +"bc:24:11:98:31:65","10.0.10.178",openmediavault,,Dynamic,"openmediavault 31:65","2025-11-15T11:19:16.000-06:00" +"bc:24:11:a8:ff:0b","10.0.10.108",docker,,Dynamic,"docker ff:0b","2025-11-15T13:52:10.000-06:00" +"bc:24:11:f9:12:5b","10.0.10.71",spoolman,,Dynamic,"spoolman 12:5b","2025-11-15T15:31:55.000-06:00" +"c4:3c:b0:fe:53:94","10.0.10.168",,,Dynamic,"Samsung Galaxy S8 Plus 53:94","2025-11-15T12:54:01.000-06:00" +"c6:f7:66:6e:fc:23","10.0.10.188",iPhone,,Dynamic,"iPhone fc:23","2025-11-15T09:27:57.000-06:00" +"cc:ba:97:21:4c:f8","10.0.10.167",,,Dynamic,"Bambu Lab A1 4c:f8","2025-11-15T15:14:51.000-06:00" +"ce:cb:0f:e1:86:8b","10.0.10.155",Watch,,Dynamic,"Watch 86:8b","2025-11-15T15:56:41.000-06:00" +"d4:a6:51:98:45:62","10.0.10.57",wlan0,,Dynamic,"wlan0 45:62","2025-11-15T07:56:44.000-06:00" +"dc:a4:ca:ea:cc:be","10.0.10.78","freds-airport-extreme",,Dynamic,"freds-airport-extreme cc:be","2025-11-15T13:35:48.000-06:00" +"de:35:17:a0:28:42","10.0.10.94",iPhone,,Dynamic,"iPhone 28:42","2025-11-15T05:00:04.000-06:00" +"e4:54:e8:50:90:af","10.0.10.2",,"proxmox.nianticbooks.home",Fixed,"In house Proxmox","1969-12-31T18:00:00.000-06:00" +"e8:4c:4a:12:03:32","10.0.10.176",,,Dynamic,"Blink Add-On Sync Module 2 03:32","2025-11-15T08:43:00.000-06:00" +"f8:e4:e3:f3:a7:69","10.0.10.253",debian,,Dynamic,"debian a7:69","2025-11-15T06:51:17.000-06:00" +"fa:2e:aa:f1:66:a3","10.0.10.61",iPhone,,Dynamic,"iPhone 66:a3","2025-11-15T15:53:32.000-06:00" \ No newline at end of file diff --git a/infrastructure/dhcp-export-all-2025-11-17T04-56-03.591Z.csv b/infrastructure/dhcp-export-all-2025-11-17T04-56-03.591Z.csv new file mode 100644 index 0000000..8995310 --- /dev/null +++ b/infrastructure/dhcp-export-all-2025-11-17T04-56-03.591Z.csv @@ -0,0 +1,55 @@ +"MAC Address","IP Address",Hostname,"Local DNS Record","Lease Type",Name,"Expiration Time" +"02:b7:f9:f1:b9:67","10.0.10.64","pelican-wings",,Dynamic,"pelican-wings b9:67","2025-11-17T22:26:47.000-06:00" +"02:f5:e9:54:36:28","10.0.10.194",homeassistant,,Dynamic,"homeassistant 36:28","2025-11-17T15:28:37.000-06:00" +"10:d5:61:3b:ce:32","10.0.10.76",wlan0,,Dynamic,"wlan0 ce:32","2025-11-17T20:15:44.000-06:00" +"32:5a:63:c9:e9:d7","10.0.10.129",Watch,,Dynamic,"Watch e9:d7","2025-11-17T18:00:17.000-06:00" +"44:61:32:90:e0:a3","10.0.10.102","My-ecobee",,Dynamic,"My-ecobee e0:a3","2025-11-17T20:15:23.000-06:00" +"62:03:ac:1f:89:40","10.0.10.202",iPhone,,Dynamic,"iPhone 89:40","2025-11-17T12:15:23.000-06:00" +"64:5d:86:15:de:20","10.0.10.157",KobePC,,Dynamic,"KobePC de:20","2025-11-17T21:29:52.000-06:00" +"64:da:ed:1c:b5:6d","10.0.10.227",eero,,Dynamic,"eero b5:6d","2025-11-17T22:49:11.000-06:00" +"64:da:ed:29:12:ad","10.0.10.101",eero,,Dynamic,"eero 12:ad","2025-11-17T18:23:32.000-06:00" +"64:da:ed:29:2e:8d","10.0.10.216",eero,,Dynamic,"eero 2e:8d","2025-11-17T17:27:16.000-06:00" +"68:57:2d:b4:dd:25","10.0.10.170","TY_WR",,Dynamic,"TY_WR dd:25","2025-11-17T21:11:34.000-06:00" +"6c:c8:40:05:5c:68","10.0.10.173","esphome-web-055c68",,Dynamic,"esphome-web-055c68 5c:68","2025-11-17T15:24:13.000-06:00" +"70:66:2a:65:36:bc","10.0.10.174",,,Dynamic,"Sony PlayStation 5 36:bc","2025-11-17T13:09:26.000-06:00" +"70:89:76:ba:0f:d4","10.0.10.81",wlan0,,Dynamic,"wlan0 0f:d4","2025-11-17T17:44:47.000-06:00" +"70:89:76:bc:f4:a4","10.0.10.151",wlan0,,Dynamic,"wlan0 f4:a4","2025-11-17T17:39:55.000-06:00" +"7c:f6:66:44:68:f6","10.0.10.201",wlan0,,Dynamic,"wlan0 68:f6","2025-11-17T21:32:53.000-06:00" +"7c:f6:66:45:44:44","10.0.10.160",wlan0,,Dynamic,"wlan0 44:44","2025-11-17T21:32:15.000-06:00" +"7e:e4:a4:b8:c2:f3","10.0.10.55",iPhone,,Dynamic,"iPhone c2:f3","2025-11-17T15:33:13.000-06:00" +"80:00:6e:f2:13:52","10.0.10.105","Freds-Mac-Pro",,Dynamic,"Jill's MacPro","2025-11-17T17:30:26.000-06:00" +"84:d6:c5:4a:70:32","10.0.10.62",,,Dynamic,"Solaredge SE7K 70:32","2025-11-17T18:46:47.000-06:00" +"84:f3:eb:c1:dd:aa","10.0.10.90","ESP_C1DDAA",,Dynamic,"ESP_C1DDAA dd:aa","2025-11-17T22:16:20.000-06:00" +"88:a9:a7:99:c3:64","10.0.10.30",AD5M,"AD5M.nianticbooks.home",Fixed,ad5m,"1969-12-31T18:00:00.000-06:00" +"88:e7:12:04:fc:06","10.0.10.195","MICRW_88_E7_12_04_FC_06",,Dynamic,"MICRW_88_E7_12_04_FC_06 fc:06","2025-11-17T15:01:03.000-06:00" +"90:de:80:80:e7:04","10.0.10.10","HOMELAB-COMMAND","HOMELAB-COMMAND.nianticbooks.home",Fixed,"Dad's PC","1969-12-31T18:00:00.000-06:00" +"a0:ad:9f:30:8c:af","10.0.10.213","Kevin-PC",,Dynamic,"Kevin-PC 8c:af","2025-11-17T19:06:01.000-06:00" +"a8:2c:3e:bc:e2:bf","10.0.10.235",,,Dynamic,"Jill's Monitor","2025-11-17T15:30:12.000-06:00" +"ac:41:6a:69:3a:8e","10.0.10.154",,,Dynamic,"Blink XT Security Camera 3a:8e","2025-11-17T18:16:05.000-06:00" +"b4:b5:2f:ea:8c:30","10.0.10.13","ilo.nianticbooks.home","ilo.nianticbooks.home",Fixed,"HP iLO","1969-12-31T18:00:00.000-06:00" +"b8:09:8a:ca:6c:53","10.0.10.144","Freds-iMac",,Dynamic,"Freds-iMac 6c:53","2025-11-17T13:19:12.000-06:00" +"bc:24:11:0f:78:84","10.0.10.79","pve-scripts-local",,Dynamic,"pve-scripts-local 78:84","2025-11-17T15:46:18.000-06:00" +"bc:24:11:44:6f:49","10.0.10.117",esphome,,Dynamic,"esphome 6f:49","2025-11-17T22:27:22.000-06:00" +"bc:24:11:4a:42:07","10.0.10.104",dockge,,Dynamic,"dockge 42:07","2025-11-17T22:03:43.000-06:00" +"bc:24:11:4a:80:e5","10.0.10.247","twingate-connector",,Dynamic,"twingate-connector 80:e5","2025-11-17T22:28:07.000-06:00" +"bc:24:11:57:dd:94","10.0.10.112",authelia,,Dynamic,"authelia dd:94","2025-11-17T13:35:27.000-06:00" +"bc:24:11:6e:22:c3","10.0.10.77",ollama,,Dynamic,"ollama 22:c3","2025-11-17T22:25:02.000-06:00" +"bc:24:11:6f:8b:2c","10.0.10.236","pelican-panel",,Dynamic,"pelican-panel 8b:2c","2025-11-17T22:25:48.000-06:00" +"bc:24:11:74:bb:f8","10.0.10.212","pelican-wings",,Dynamic,"pelican-wings bb:f8","2025-11-17T22:26:32.000-06:00" +"bc:24:11:86:f0:c8","10.0.10.164","bar-assistant",,Dynamic,"bar-assistant f0:c8","2025-11-17T22:24:17.000-06:00" +"bc:24:11:8f:eb:eb","10.0.10.113",esphome,,Dynamic,"esphome eb:eb","2025-11-17T12:56:37.000-06:00" +"bc:24:11:98:31:65","10.0.10.178",openmediavault,,Dynamic,"openmediavault 31:65","2025-11-17T19:01:17.000-06:00" +"bc:24:11:a8:ff:0b","10.0.10.108",docker,,Dynamic,"docker ff:0b","2025-11-17T15:38:40.000-06:00" +"bc:24:11:d3:34:1c","10.0.10.192","twingate-connector",,Dynamic,"twingate-connector 34:1c","2025-11-17T22:28:52.000-06:00" +"bc:24:11:f9:12:5b","10.0.10.71",spoolman,,Dynamic,"spoolman 12:5b","2025-11-17T14:48:23.000-06:00" +"c4:3c:b0:fe:53:94","10.0.10.168",,,Dynamic,"Samsung Galaxy S8 Plus 53:94","2025-11-17T12:54:02.000-06:00" +"c6:f7:66:6e:fc:23","10.0.10.188",iPhone,,Dynamic,"iPhone fc:23","2025-11-17T14:31:23.000-06:00" +"cc:ba:97:21:4c:f8","10.0.10.167",,,Dynamic,"Bambu Lab A1 4c:f8","2025-11-17T22:08:36.000-06:00" +"ce:cb:0f:e1:86:8b","10.0.10.155",Watch,,Dynamic,"Watch 86:8b","2025-11-17T12:43:20.000-06:00" +"d0:c9:07:89:89:6c","10.0.10.80",,,Dynamic,"Govee H5082 89:6c","2025-11-17T19:45:10.000-06:00" +"d4:a6:51:98:45:62","10.0.10.57",wlan0,,Dynamic,"wlan0 45:62","2025-11-17T19:28:46.000-06:00" +"dc:a4:ca:ea:cc:be","10.0.10.78","freds-airport-extreme",,Dynamic,"freds-airport-extreme cc:be","2025-11-17T22:06:03.000-06:00" +"de:35:17:a0:28:42","10.0.10.94",iPhone,,Dynamic,"iPhone 28:42","2025-11-17T21:29:25.000-06:00" +"e4:54:e8:50:90:af","10.0.10.2",,"proxmox.nianticbooks.home",Fixed,"In house Proxmox","1969-12-31T18:00:00.000-06:00" +"e8:4c:4a:12:03:32","10.0.10.176",,,Dynamic,"Blink Add-On Sync Module 2 03:32","2025-11-17T20:43:01.000-06:00" +"fa:2e:aa:f1:66:a3","10.0.10.61",iPhone,,Dynamic,"iPhone 66:a3","2025-11-17T12:14:51.000-06:00" \ No newline at end of file diff --git a/infrastructure/docs/index.html b/infrastructure/docs/index.html new file mode 100644 index 0000000..fc3476f --- /dev/null +++ b/infrastructure/docs/index.html @@ -0,0 +1,17 @@ + + + + +Directory listing for /infrastructure/docs/ + + +

Directory listing for /infrastructure/docs/

+
+ +
+ + diff --git a/infrastructure/esphome/index.html b/infrastructure/esphome/index.html new file mode 100644 index 0000000..4f18500 --- /dev/null +++ b/infrastructure/esphome/index.html @@ -0,0 +1,21 @@ + + + + +Directory listing for /infrastructure/esphome/ + + +

Directory listing for /infrastructure/esphome/

+
+ +
+ + diff --git a/infrastructure/guides/index.html b/infrastructure/guides/index.html new file mode 100644 index 0000000..9d17f75 --- /dev/null +++ b/infrastructure/guides/index.html @@ -0,0 +1,27 @@ + + + + +Directory listing for /infrastructure/guides/ + + +

Directory listing for /infrastructure/guides/

+
+ +
+ + diff --git a/infrastructure/home-assistant/index.html b/infrastructure/home-assistant/index.html new file mode 100644 index 0000000..05fb2f6 --- /dev/null +++ b/infrastructure/home-assistant/index.html @@ -0,0 +1,82 @@ + + + + +Directory listing for /infrastructure/home-assistant/ + + +

Directory listing for /infrastructure/home-assistant/

+
+ +
+ + diff --git a/infrastructure/homelab-dashboard/index.html b/infrastructure/homelab-dashboard/index.html new file mode 100644 index 0000000..f24a85d --- /dev/null +++ b/infrastructure/homelab-dashboard/index.html @@ -0,0 +1,282 @@ + + + + + + Homelab Dashboard + + + + + + + + diff --git a/infrastructure/index.html b/infrastructure/index.html new file mode 100644 index 0000000..9d32cb2 --- /dev/null +++ b/infrastructure/index.html @@ -0,0 +1,76 @@ + + + + +Directory listing for /infrastructure/ + + +

Directory listing for /infrastructure/

+
+ +
+ + diff --git a/infrastructure/infrastructure-audit.md b/infrastructure/infrastructure-audit.md new file mode 100644 index 0000000..157f2e8 --- /dev/null +++ b/infrastructure/infrastructure-audit.md @@ -0,0 +1,290 @@ +# Infrastructure Audit + +**Last Updated:** 2026-01-18 +**Status:** Active - Source of Truth + +This document provides a comprehensive inventory of all infrastructure components. For IP allocations, see `IP-ALLOCATION.md`. + +--- + +## 1. VPS Configuration + +| Property | Value | +|----------|-------| +| Provider | Hudson Valley Host | +| Public IP | 66.63.182.168 | +| Hostname | vps.nianticbooks.com | +| OS | Ubuntu 24.04 x86_64 | +| Specs | 2 vCPUs, 4GB RAM, 100GB storage | + +### VPS Services + +| Service | Port | Status | +|---------|------|--------| +| Caddy Reverse Proxy | 80, 443 | Active | +| WireGuard VPN Server | 51820/UDP | Active | +| RustDesk Relay (hbbr) | 21117 | Active | + +### Caddy Routes (via WireGuard to home lab) + +| Domain | Backend | Status | +|--------|---------|--------| +| freddesk.nianticbooks.com | 10.0.10.3:8006 | Active | +| ad5m.nianticbooks.com | 10.0.10.30:80 | Active | +| bob.nianticbooks.com | 10.0.10.24:8123 | Active | +| auth.nianticbooks.com | 10.0.10.21:9000 | Active | +| cocktails.nianticbooks.com | 10.0.10.40 | Active | + +--- + +## 2. WireGuard Tunnel + +| Property | Value | +|----------|-------| +| Status | Active | +| Gaming VPS Endpoint | 51.222.12.162:51820 | +| Gaming VPS Tunnel IP | 10.0.9.1 | +| UCG Ultra Tunnel IP | 10.0.9.2 | +| VPS Proxy Tunnel IP | 10.0.9.3 | +| Home Lab Subnet | 10.0.10.0/24 | +| Keepalive | 25 seconds | + +--- + +## 3. Proxmox Cluster + +### main-pve (DL380p) - Production Workloads + +| Property | Value | +|----------|-------| +| IP Address | 10.0.10.3 (static) | +| iLO Management | 10.0.10.13 | +| Location | Remote | +| CPU | 32 cores | +| RAM | 96 GB | +| Role | Primary production host | + +**Running Containers (14 total):** + +| CT ID | Name | IP | Service | +|-------|------|-----|---------| +| 102 | postgresql | 10.0.10.20 | Shared PostgreSQL database | +| 103 | bar-assistant | 10.0.10.40 | Cocktail recipe manager | +| 105 | pterodactyl-panel | 10.0.10.45 | Game server management panel | +| 106 | n8n | 10.0.10.22 | Workflow automation | +| 107 | pterodactyl-wings | 10.0.10.46 | Game server node | +| 115 | ca-server | 10.0.10.15 | Step-CA certificate authority | +| 121 | authentik | 10.0.10.21 | SSO/Identity provider | +| 123 | rustdesk | 10.0.10.23 | RustDesk ID server (hbbs) | +| 125 | prometheus | 10.0.10.25 | Monitoring (Prometheus + Grafana) | +| 127 | dockge | 10.0.10.27 | Docker Compose mgmt + Media Stack (6 services) | +| 128 | uptime-kuma | 10.0.10.26 | Uptime monitoring | +| 130 | minecraft-forge | 10.0.10.41 | Minecraft Forge server | +| 131 | minecraft-stoneblock4 | 10.0.10.42 | Minecraft Stoneblock 4 | +| 135 | vehicle-tracker | 10.0.10.35 | Vehicle Maintenance Tracker (Planned) | + +### pve-router (i5) - Local/Light Workloads + +| Property | Value | +|----------|-------| +| IP Address | 10.0.10.2 (static) | +| DNS | proxmox.nianticbooks.home | +| Location | Office | +| CPU | 8 cores | +| RAM | 8 GB | +| Role | Local development, Home Assistant | + +**Running VMs (1 total):** + +| VM ID | Name | IP | Service | +|-------|------|-----|---------| +| 104 | haos16.2 | 10.0.10.24 | Home Assistant OS | + +**Running Containers (1 total):** + +| CT ID | Name | IP | Service | +|-------|------|-----|---------| +| 101 | twingate-connector | 10.0.10.179 | Zero-trust remote access | + +### pve-storage - Storage Host + +| Property | Value | +|----------|-------| +| IP Address | 10.0.10.4 (static) | +| Role | Storage host (3.5" drive support) | + +**Running VMs (1 total):** + +| VM ID | Name | IP | Service | +|-------|------|-----|---------| +| 400 | OMV | 10.0.10.5 | OpenMediaVault (12TB) | + +--- + +## 4. Network Configuration + +| Property | Value | +|----------|-------| +| Subnet | 10.0.10.0/24 | +| Gateway | 10.0.10.1 (UCG Ultra) | +| DHCP Range | 10.0.10.50-254 | +| Static Range | 10.0.10.1-49 | + +**Note:** All infrastructure IPs (.1-.49) use static configuration on devices, not DHCP reservations. + +See `IP-ALLOCATION.md` for complete IP assignments. + +--- + +## 5. Key Services Summary + +### Authentication & Security + +| Service | IP | Port | Purpose | +|---------|-----|------|---------| +| Authentik SSO | 10.0.10.21 | 9000 | OAuth2/OIDC, WebAuthn | +| Step-CA | 10.0.10.15 | 8443 | Internal certificate authority | +| Twingate | 10.0.10.179 | - | Zero-trust remote access | + +### Databases + +| Service | IP | Port | Purpose | +|---------|-----|------|---------| +| PostgreSQL | 10.0.10.20 | 5432 | Shared DB (Authentik, n8n, RustDesk, Grafana) | + +### Monitoring + +| Service | IP | Port | Purpose | +|---------|-----|------|---------| +| Prometheus | 10.0.10.25 | 9090 | Metrics collection | +| Grafana | 10.0.10.25 | 3000 | Dashboards | +| Uptime Kuma | 10.0.10.26 | 3001 | Uptime monitoring | + +### Automation + +| Service | IP | Port | Purpose | +|---------|-----|------|---------| +| n8n | 10.0.10.22 | 5678 | Workflow automation | +| Home Assistant | 10.0.10.24 | 8123 | Smart home | + +### Gaming + +| Service | IP | Port | Purpose | +|---------|-----|------|---------| +| Pterodactyl Panel | 10.0.10.45 | 80 | Game server management | +| Pterodactyl Wings | 10.0.10.46 | 8080 | Game server node | +| Minecraft Forge | 10.0.10.41 | 25565 | CFMRPGU modpack | +| Minecraft SB4 | 10.0.10.42 | 25566 | Stoneblock 4 modpack | + +### Remote Access + +| Service | IP | Port | Purpose | +|---------|-----|------|---------| +| RustDesk ID (hbbs) | 10.0.10.23 | 21116 | Remote desktop ID server | +| RustDesk Relay (hbbr) | VPS | 21117 | Remote desktop relay | + +### Storage + +| Service | IP | Purpose | +|---------|-----|---------| +| OpenMediaVault | 10.0.10.5 | 12TB NFS/SMB storage (media library for Arr stack) | +| Dockge | 10.0.10.27 | Docker stack management | + +### Media Automation (Arr Stack) + +| Service | IP | Port | Purpose | +|---------|-----|------|---------| +| Sonarr | 10.0.10.27 | 8989 | TV show monitoring & automation | +| Radarr | 10.0.10.27 | 7878 | Movie monitoring & automation | +| Prowlarr | 10.0.10.27 | 9696 | Indexer management for *arr apps | +| Bazarr | 10.0.10.27 | 6767 | Subtitle download automation | +| Deluge | 10.0.10.27 | 8112 | BitTorrent download client | +| Calibre-Web | 10.0.10.27 | 8083 | eBook library management | +| Caddy Internal Proxy | 10.0.10.27 | 443 | HTTPS reverse proxy (Caddy Internal PKI) | + +**Storage Paths:** +- `/media/tv` - Sonarr TV library +- `/media/movies` - Radarr movie library +- `/media/downloads` - Deluge download directory +- `/media/books` - Calibre library + +**Note:** All services run as Docker containers on CT 127 (Dockge), accessible via HTTPS at `https://.nianticbooks.home` + +### Utility + +| Service | IP | Port | Purpose | +|---------|-----|------|---------| +| Bar Assistant | 10.0.10.40 | 80 | Cocktail recipe manager | +| Vikunja | 10.0.10.27 | 3456 | Task management (no longer actively used) | + +--- + +## 6. Backup System + +### Tier 1 - Local (OMV NFS) + +| Property | Value | +|----------|-------| +| Storage | 10.0.10.5:/export/backups | +| Available | 7.3 TB | +| Mount Point | /mnt/omv-backups (all Proxmox hosts) | + +**Automated Backups:** + +| Time | What | Retention | +|------|------|-----------| +| 2:00 AM | PostgreSQL (all databases) | 7 daily, 4 weekly, 3 monthly | +| 2:30 AM | Proxmox VMs/containers | 7 daily, 4 weekly, 3 monthly | + +--- + +## 7. Physical Devices + +### HOMELAB-COMMAND (10.0.10.10) + +| Property | Value | +|----------|-------| +| Type | Gaming PC | +| GPU | RTX 5060 | +| Services | Wyoming (Whisper STT, Piper TTS), Ollama LLM | +| OS | Windows 11 | +| Role | Claude Code host, voice assistant hub | + +### HP iLO (10.0.10.13) + +| Property | Value | +|----------|-------| +| Type | Server management | +| Purpose | DL380p (main-pve) remote management | + +### 3D Printers + +| Device | IP | Status | +|--------|-----|--------| +| Flashforge AD5M | 10.0.10.30 | Active | +| Bambu Lab A1 | 10.0.10.31 | Active | + +--- + +## 8. Audit History + +| Date | Action | Notes | +|------|--------|-------| +| 2026-01-25 | Deployed Media Stack | Sonarr, Radarr, Prowlarr, Bazarr, Deluge, Calibre-Web on CT 127 via Docker | +| 2026-01-25 | Deployed Caddy Internal Proxy | HTTPS reverse proxy for internal services on CT 127 | +| 2026-01-25 | Deployed CA certificates | Homelab root CA distributed to all LXC containers and Proxmox hosts | +| 2026-01-25 | Deprecated Vikunja | No longer actively used (Claude Code replaced n8n workflow use case) | +| 2026-01-18 | Deployed Vikunja | Task management on Dockge (10.0.10.27:3456), tasks.nianticbooks.com | +| 2026-01-13 | Full network audit | Compared UCG DHCP export vs docs, verified all services | +| 2026-01-13 | Removed CT 100 | pve-scripts-local - unused, IP conflict with bar-assistant | +| 2025-12-29 | Initial audit | Infrastructure audit template completed | + +--- + +## 9. Outstanding Items + +- [ ] Fix Home Assistant public domain (Caddy HTTPS backend config) +- [x] Move Bambu A1 to static IP 10.0.10.31 (done 2026-01-13) +- [ ] Identify unknown Raspberry Pi devices (.81, .171, .246) +- [ ] Document ESP devices purpose (.90, .207) +- [ ] Cleanup deprecated VMs (Spoolman .71, Authelia .112) diff --git a/infrastructure/install-ca-cert-admin.ps1 b/infrastructure/install-ca-cert-admin.ps1 new file mode 100644 index 0000000..dfcb667 --- /dev/null +++ b/infrastructure/install-ca-cert-admin.ps1 @@ -0,0 +1,36 @@ +# Run this script as Administrator to install CA cert system-wide +# Right-click this file and select "Run with PowerShell" + +$certPath = "C:\Users\Fred\projects\infrastructure\Homelab-Root-CA.crt" + +Write-Host "Installing Homelab CA Certificate (System-Wide)..." -ForegroundColor Cyan +Write-Host "" + +try { + $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 + $cert.Import($certPath) + + $store = New-Object System.Security.Cryptography.X509Certificates.X509Store('Root','LocalMachine') + $store.Open('ReadWrite') + $store.Add($cert) + $store.Close() + + Write-Host "SUCCESS! CA certificate installed system-wide!" -ForegroundColor Green + Write-Host "" + Write-Host "Certificate Details:" -ForegroundColor Cyan + Write-Host " Subject: $($cert.Subject)" -ForegroundColor White + Write-Host " Issuer: $($cert.Issuer)" -ForegroundColor White + Write-Host " Valid Until: $($cert.NotAfter)" -ForegroundColor White + Write-Host "" + Write-Host "Close and reopen your browser, then visit:" -ForegroundColor Yellow + Write-Host " https://10.0.10.24:8123" -ForegroundColor White + +} catch { + Write-Host "ERROR: $($_.Exception.Message)" -ForegroundColor Red + Write-Host "" + Write-Host "Make sure you ran this script as Administrator!" -ForegroundColor Yellow +} + +Write-Host "" +Write-Host "Press any key to exit..." +$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') diff --git a/infrastructure/mc_server/index.html b/infrastructure/mc_server/index.html new file mode 100644 index 0000000..ab936a7 --- /dev/null +++ b/infrastructure/mc_server/index.html @@ -0,0 +1,28 @@ + + + + +Directory listing for /infrastructure/mc_server/ + + +

Directory listing for /infrastructure/mc_server/

+
+ +
+ + diff --git a/infrastructure/n8n-workflows/index.html b/infrastructure/n8n-workflows/index.html new file mode 100644 index 0000000..1063416 --- /dev/null +++ b/infrastructure/n8n-workflows/index.html @@ -0,0 +1,40 @@ + + + + +Directory listing for /infrastructure/n8n-workflows/ + + +

Directory listing for /infrastructure/n8n-workflows/

+
+ +
+ + diff --git a/infrastructure/prometheus-config.yml b/infrastructure/prometheus-config.yml new file mode 100644 index 0000000..f5945ab --- /dev/null +++ b/infrastructure/prometheus-config.yml @@ -0,0 +1,240 @@ +# Prometheus Configuration for Fred's Homelab +# Last Updated: 2025-12-25 +# +# Installation Instructions: +# 1. Install node_exporter on each host you want to monitor +# 2. Copy this file to 10.0.10.25:/etc/prometheus/prometheus.yml +# 3. Restart Prometheus: systemctl restart prometheus +# 4. Verify targets: http://10.0.10.25:9090/targets + +global: + scrape_interval: 15s + evaluation_interval: 15s + external_labels: + environment: 'homelab' + datacenter: 'home' + +# Alertmanager configuration (optional - configure later) +alerting: + alertmanagers: + - static_configs: + - targets: + # - alertmanager:9093 + +# Load alerting rules +rule_files: + # - "/etc/prometheus/alerts/*.yml" + +scrape_configs: + # ==================================== + # Prometheus Self-Monitoring + # ==================================== + - job_name: 'prometheus' + static_configs: + - targets: ['localhost:9090'] + labels: + app: 'prometheus' + instance: 'prometheus-main' + + # ==================================== + # Proxmox Hosts (Node Exporters) + # ==================================== + # Install: apt install prometheus-node-exporter + + - job_name: 'proxmox-nodes' + static_configs: + - targets: ['10.0.10.2:9100'] # pve-router (i5) + labels: + hostname: 'pve-router' + role: 'proxmox-host' + location: 'office' + + - targets: ['10.0.10.3:9100'] # main-pve (DL380p) + labels: + hostname: 'main-pve' + role: 'proxmox-host' + location: 'remote' + + - targets: ['10.0.10.4:9100'] # backup-pve + labels: + hostname: 'backup-pve' + role: 'proxmox-host' + location: 'storage' + + # ==================================== + # Proxmox VE API Metrics (Proxmox Exporter) + # ==================================== + # Optional: Install https://github.com/prometheus-pve/prometheus-pve-exporter + # This gives you VM/CT specific metrics + + # - job_name: 'proxmox-api' + # static_configs: + # - targets: ['10.0.10.25:9221'] # Run pve_exporter on prometheus host + # labels: + # exporter: 'pve_exporter' + + # ==================================== + # VPS (Node Exporter) + # ==================================== + # Install on VPS: apt install prometheus-node-exporter + + - job_name: 'vps' + static_configs: + - targets: ['66.63.182.168:9100'] # VPS external IP + labels: + hostname: 'vps-hv' + role: 'vps' + provider: 'hudson-valley-host' + + # ==================================== + # Gaming PC / HOMELAB-COMMAND + # ==================================== + # Windows: Use windows_exporter + # https://github.com/prometheus-community/windows_exporter + + - job_name: 'gaming-pc' + static_configs: + - targets: ['10.0.10.10:9182'] # windows_exporter default port + labels: + hostname: 'HOMELAB-COMMAND' + role: 'workstation' + os: 'windows-11' + + # ==================================== + # Databases + # ==================================== + + # PostgreSQL Exporter + # Install: https://github.com/prometheus-community/postgres_exporter + - job_name: 'postgresql' + static_configs: + - targets: ['10.0.10.20:9187'] + labels: + hostname: 'postgresql' + role: 'database' + app: 'postgres' + + # ==================================== + # Application Services + # ==================================== + + # Authentik (built-in metrics) + - job_name: 'authentik' + metrics_path: '/application/o/prometheus-outpost/metrics/' + static_configs: + - targets: ['10.0.10.21:9000'] + labels: + app: 'authentik' + role: 'sso' + + # n8n (if metrics enabled) + # - job_name: 'n8n' + # static_configs: + # - targets: ['10.0.10.22:5678'] + # labels: + # app: 'n8n' + + # Home Assistant (via integration) + # https://www.home-assistant.io/integrations/prometheus/ + - job_name: 'homeassistant' + metrics_path: '/api/prometheus' + bearer_token: 'YOUR_LONG_LIVED_ACCESS_TOKEN' # Create in HA: Profile -> Long-Lived Access Tokens + static_configs: + - targets: ['10.0.10.24:8123'] + labels: + app: 'homeassistant' + role: 'automation' + + # Caddy (metrics built-in, needs admin API enabled) + # - job_name: 'caddy' + # static_configs: + # - targets: ['66.63.182.168:2019'] # Caddy admin API + # labels: + # app: 'caddy' + # role: 'reverse-proxy' + + # ==================================== + # Network Devices + # ==================================== + + # UCG Ultra (SNMP Exporter) + # https://github.com/prometheus/snmp_exporter + # - job_name: 'unifi-gateway' + # static_configs: + # - targets: ['10.0.10.1'] + # labels: + # device: 'ucg-ultra' + # role: 'gateway' + + # ==================================== + # Storage + # ==================================== + + # OpenMediaVault Node Exporter + - job_name: 'storage' + static_configs: + - targets: ['10.0.10.5:9100'] + labels: + hostname: 'openmediavault' + role: 'storage' + capacity: '12tb' + + # ==================================== + # Blackbox Exporter (Endpoint Monitoring) + # ==================================== + # Optional: Monitor HTTP endpoints, SSL certs, DNS, etc. + # https://github.com/prometheus/blackbox_exporter + + # - job_name: 'blackbox-http' + # metrics_path: /probe + # params: + # module: [http_2xx] + # static_configs: + # - targets: + # - https://auth.nianticbooks.com + # - https://freddesk.nianticbooks.com + # - https://bob.nianticbooks.com + # relabel_configs: + # - source_labels: [__address__] + # target_label: __param_target + # - source_labels: [__param_target] + # target_label: instance + # - target_label: __address__ + # replacement: localhost:9115 # Blackbox exporter address + +# ==================================== +# Installation Quick Reference +# ==================================== +# +# Node Exporter (Debian/Ubuntu): +# apt update && apt install prometheus-node-exporter -y +# systemctl enable prometheus-node-exporter +# systemctl start prometheus-node-exporter +# # Verify: curl http://localhost:9100/metrics +# +# Node Exporter (Manual install): +# wget https://github.com/prometheus/node_exporter/releases/download/v1.7.0/node_exporter-1.7.0.linux-amd64.tar.gz +# tar xvfz node_exporter-*.tar.gz +# sudo mv node_exporter-*/node_exporter /usr/local/bin/ +# sudo useradd -rs /bin/false node_exporter +# +# # Create systemd service: /etc/systemd/system/node_exporter.service +# sudo systemctl daemon-reload +# sudo systemctl enable node_exporter +# sudo systemctl start node_exporter +# +# Windows Exporter: +# Download: https://github.com/prometheus-community/windows_exporter/releases +# Install MSI package +# Verify: http://localhost:9182/metrics +# +# PostgreSQL Exporter: +# apt install prometheus-postgres-exporter -y +# # Configure connection in /etc/default/prometheus-postgres-exporter +# # DATA_SOURCE_NAME="postgresql://user:pass@localhost:5432/postgres?sslmode=disable" +# +# Home Assistant Prometheus Integration: +# 1. Settings -> Devices & Services -> Add Integration +# 2. Search "Prometheus" +# 3. Configure +# 4. Create long-lived access token: Profile -> Security -> Long-Lived Access Tokens diff --git a/infrastructure/pterodactyl/index.html b/infrastructure/pterodactyl/index.html new file mode 100644 index 0000000..1d978d2 --- /dev/null +++ b/infrastructure/pterodactyl/index.html @@ -0,0 +1,24 @@ + + + + +Directory listing for /infrastructure/pterodactyl/ + + +

Directory listing for /infrastructure/pterodactyl/

+
+ +
+ + diff --git a/infrastructure/scripts/index.html b/infrastructure/scripts/index.html new file mode 100644 index 0000000..ba74020 --- /dev/null +++ b/infrastructure/scripts/index.html @@ -0,0 +1,37 @@ + + + + +Directory listing for /infrastructure/scripts/ + + +

Directory listing for /infrastructure/scripts/

+
+ +
+ + diff --git a/infrastructure/temp_scan.ps1 b/infrastructure/temp_scan.ps1 new file mode 100644 index 0000000..b1fb758 --- /dev/null +++ b/infrastructure/temp_scan.ps1 @@ -0,0 +1,40 @@ +# Quick network scan of infrastructure IPs +$ips = @( + "10.0.10.1", # UCG Ultra + "10.0.10.2", # pve-router + "10.0.10.3", # main-pve + "10.0.10.4", # pve-storage + "10.0.10.5", # openmediavault + "10.0.10.10", # HOMELAB-COMMAND + "10.0.10.13", # HP iLO + "10.0.10.15", # CA Server + "10.0.10.20", # PostgreSQL + "10.0.10.21", # Authentik + "10.0.10.22", # n8n + "10.0.10.23", # RustDesk + "10.0.10.24", # Home Assistant + "10.0.10.25", # Monitoring (Prometheus/Grafana) + "10.0.10.26", # Uptime Kuma + "10.0.10.27", # Dockge + "10.0.10.28", # ESPHome (deprecated) + "10.0.10.30", # ad5m 3D printer + "10.0.10.40", # Bar Assistant + "10.0.10.41", # Minecraft + "10.0.10.88" # Web Power Switch +) + +Write-Host "Scanning documented infrastructure IPs..." +$results = @() + +foreach ($ip in $ips) { + $online = Test-Connection -ComputerName $ip -Count 1 -Quiet -TimeoutSeconds 1 + $status = if ($online) { "UP" } else { "DOWN" } + $results += [PSCustomObject]@{ + IP = $ip + Status = $status + } + Write-Host "$ip - $status" +} + +Write-Host "`nSummary:" +$results | Format-Table -AutoSize diff --git a/install-node-exporters.sh b/install-node-exporters.sh new file mode 100755 index 0000000..fccce8d --- /dev/null +++ b/install-node-exporters.sh @@ -0,0 +1,57 @@ +#!/bin/bash +# Install node_exporter on hosts missing monitoring +# Run this from your PC/terminal + +echo "๐Ÿš€ Installing node_exporter on missing hosts..." +echo "" + +# 1. pve-router (10.0.10.2) +echo "๐Ÿ“ฆ Installing on pve-router (10.0.10.2)..." +ssh root@10.0.10.2 "apt update && apt install prometheus-node-exporter -y && systemctl enable --now prometheus-node-exporter" +if [ $? -eq 0 ]; then + echo "โœ… pve-router node_exporter installed and running" +else + echo "โŒ Failed to install on pve-router" +fi +echo "" + +# 2. OVH Gaming VPS (51.222.12.162) +echo "๐Ÿ“ฆ Installing on vps-gaming (51.222.12.162)..." +ssh root@51.222.12.162 "apt update && apt install prometheus-node-exporter -y && systemctl enable --now prometheus-node-exporter && ufw allow 9100/tcp" +if [ $? -eq 0 ]; then + echo "โœ… vps-gaming node_exporter installed and running" +else + echo "โŒ Failed to install on vps-gaming" +fi +echo "" + +# 3. OpenClaw Container (10.0.10.41) - Install on myself! +echo "๐Ÿ“ฆ Installing on OpenClaw container (10.0.10.41)..." +ssh root@10.0.10.41 "apt update && apt install prometheus-node-exporter -y && systemctl enable --now prometheus-node-exporter" +if [ $? -eq 0 ]; then + echo "โœ… OpenClaw node_exporter installed (self-monitoring enabled!)" +else + echo "โŒ Failed to install on OpenClaw" +fi +echo "" + +# Verify all targets +echo "๐Ÿ” Verifying node_exporter endpoints..." +echo "" + +echo "pve-router:" +curl -s -m 2 http://10.0.10.2:9100/metrics | head -3 || echo "โŒ Not responding" +echo "" + +echo "vps-gaming:" +curl -s -m 2 http://51.222.12.162:9100/metrics | head -3 || echo "โŒ Not responding" +echo "" + +echo "OpenClaw:" +curl -s -m 2 http://10.0.10.41:9100/metrics | head -3 || echo "โŒ Not responding" +echo "" + +echo "โœ… Installation complete!" +echo "" +echo "Check Prometheus targets in ~2 minutes:" +echo " http://10.0.10.25:9090/targets" diff --git a/install-on-prometheus.sh b/install-on-prometheus.sh new file mode 100755 index 0000000..6f5041a --- /dev/null +++ b/install-on-prometheus.sh @@ -0,0 +1,193 @@ +#!/bin/bash +# Run this script INSIDE the Prometheus container (CT 125) +# Usage: curl http://10.0.10.28/workspace/fred-infrastructure/install-on-prometheus.sh | bash + +set -e + +echo "๐Ÿš€ Installing reduced alert configuration..." +echo "" + +# Backup existing configs +echo "๐Ÿ“ฆ Backing up existing configs..." +mkdir -p /etc/prometheus/backups +cp /etc/prometheus/alertmanager.yml /etc/prometheus/backups/alertmanager.yml.$(date +%Y%m%d-%H%M%S) 2>/dev/null || true +cp /etc/prometheus/rules/homelab-alerts.yml /etc/prometheus/backups/homelab-alerts.yml.$(date +%Y%m%d-%H%M%S) 2>/dev/null || true +echo "โœ… Backups saved to /etc/prometheus/backups/" +echo "" + +# Download new configs +echo "๐Ÿ“ฅ Downloading new configurations..." + +# Alertmanager config +cat > /etc/prometheus/alertmanager.yml << 'EOF' +global: + resolve_timeout: 5m + +route: + group_by: ['alertname', 'severity', 'instance'] + group_wait: 30s + group_interval: 5m + repeat_interval: 12h + receiver: 'null' + routes: + - matchers: + - severity="critical" + receiver: 'discord-critical' + group_wait: 10s + repeat_interval: 1h + - matchers: + - severity="warning" + receiver: 'null' + repeat_interval: 24h + +inhibit_rules: + - source_matchers: + - severity="critical" + target_matchers: + - severity="warning" + equal: ['alertname', 'instance'] + - source_matchers: + - alertname="HostDown" + target_matchers: + - alertname!="HostDown" + equal: ['instance'] + +receivers: + - name: 'null' + - name: 'discord-critical' + webhook_configs: + - url: 'https://discord.com/api/webhooks/1462667503301038285/ZVJDuek6VADA-RdI09xJDvqjveOWXgxQnMBcsQzoKwVPnNOACMCL5v-HN55-KVe4IZY0' + send_resolved: true + http_config: + follow_redirects: true + max_alerts: 0 +EOF + +echo "โœ… Alertmanager config updated" + +# Prometheus alert rules +cat > /etc/prometheus/rules/homelab-alerts.yml << 'EOF' +groups: + - name: infrastructure + interval: 30s + rules: + - alert: HostDown + expr: up == 0 + for: 2m + labels: + severity: critical + category: infrastructure + annotations: + summary: "Host {{ $labels.instance }} is down" + description: "{{ $labels.instance }} has been unreachable for more than 2 minutes" + + - alert: HighCPUUsage + expr: 100 - (avg by(instance, hostname) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80 + for: 5m + labels: + severity: warning + category: performance + annotations: + summary: "High CPU usage on {{ $labels.hostname }}" + description: "CPU usage on {{ $labels.instance }} is {{ $value | humanize }}%" + + - alert: CriticalCPUUsage + expr: 100 - (avg by(instance, hostname) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 95 + for: 5m + labels: + severity: critical + category: performance + annotations: + summary: "CRITICAL CPU usage on {{ $labels.hostname }}" + description: "CPU usage on {{ $labels.instance }} is {{ $value | humanize }}%" + + - alert: HighMemoryUsage + expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 85 + for: 10m + labels: + severity: warning + category: performance + annotations: + summary: "High memory usage on {{ $labels.hostname }}" + description: "Memory usage on {{ $labels.instance }} is {{ $value | humanize }}%" + + - alert: CriticalMemoryUsage + expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 95 + for: 5m + labels: + severity: critical + category: performance + annotations: + summary: "CRITICAL memory usage on {{ $labels.hostname }}" + description: "Memory usage on {{ $labels.instance }} is {{ $value | humanize }}%" + + - name: storage + interval: 1m + rules: + - alert: DiskSpaceLow + expr: (node_filesystem_avail_bytes{fstype!~"tmpfs|fuse.lxcfs|squashfs|overlay"} / node_filesystem_size_bytes) * 100 < 15 + for: 5m + labels: + severity: warning + category: storage + annotations: + summary: "Low disk space on {{ $labels.hostname }}" + description: "Disk {{ $labels.mountpoint }} has {{ $value | humanize }}% free" + + - alert: DiskSpaceCritical + expr: (node_filesystem_avail_bytes{fstype!~"tmpfs|fuse.lxcfs|squashfs|overlay"} / node_filesystem_size_bytes) * 100 < 5 + for: 2m + labels: + severity: critical + category: storage + annotations: + summary: "CRITICAL disk space on {{ $labels.hostname }}" + description: "Disk {{ $labels.mountpoint }} has only {{ $value | humanize }}% free!" + + - name: services + interval: 1m + rules: + - alert: PostgreSQLDown + expr: up{app="postgres"} == 0 + for: 2m + labels: + severity: critical + category: database + annotations: + summary: "PostgreSQL is down" + description: "PostgreSQL has been down for more than 2 minutes" + + - alert: VPSDown + expr: up{role="vps"} == 0 + for: 2m + labels: + severity: critical + category: infrastructure + annotations: + summary: "VPS is unreachable" + description: "VPS has been unreachable for more than 2 minutes" +EOF + +echo "โœ… Alert rules updated" +echo "" + +# Reload services +echo "๐Ÿ”„ Reloading Prometheus and Alertmanager..." +systemctl reload prometheus +systemctl reload prometheus-alertmanager +echo "โœ… Services reloaded" +echo "" + +# Verify +echo "โœ… Deployment complete!" +echo "" +echo "๐Ÿ“Š New configuration:" +echo " โ€ข CPU warning: 80%+ over 5 min (logged only)" +echo " โ€ข CPU critical: 95%+ over 5 min (Discord alert)" +echo " โ€ข Only CRITICAL alerts sent to Discord" +echo " โ€ข WARNING alerts logged but NOT sent" +echo " โ€ข Email notifications disabled" +echo "" +echo "๐Ÿงช Test with:" +echo " curl -X POST http://localhost:9093/api/v1/alerts -d '[{\"labels\":{\"alertname\":\"Test\",\"severity\":\"critical\"},\"annotations\":{\"summary\":\"Test\"}}]'" +echo "" diff --git a/install-windows-exporter.ps1 b/install-windows-exporter.ps1 new file mode 100644 index 0000000..5cf7fdc --- /dev/null +++ b/install-windows-exporter.ps1 @@ -0,0 +1,98 @@ +# Windows Exporter Installation Script for HOMELAB-COMMAND +# Prometheus monitoring agent for Windows + +Write-Host "=== Windows Exporter Installation ===" -ForegroundColor Cyan +Write-Host "" + +# Download URL for latest version +$version = "0.28.1" +$downloadUrl = "https://github.com/prometheus-community/windows_exporter/releases/download/v$version/windows_exporter-$version-amd64.msi" +$installerPath = "$env:TEMP\windows_exporter.msi" + +Write-Host "Downloading Windows Exporter v$version..." -ForegroundColor Yellow +try { + Invoke-WebRequest -Uri $downloadUrl -OutFile $installerPath -UseBasicParsing + Write-Host "โœ… Download complete: $installerPath" -ForegroundColor Green +} catch { + Write-Host "โŒ Download failed: $_" -ForegroundColor Red + Write-Host "" + Write-Host "Manual download URL:" -ForegroundColor Yellow + Write-Host $downloadUrl + exit 1 +} + +Write-Host "" +Write-Host "Installing Windows Exporter..." -ForegroundColor Yellow +Write-Host "This will:" +Write-Host " - Install as a Windows service" +Write-Host " - Listen on port 9182" +Write-Host " - Start automatically on boot" +Write-Host "" + +# Install silently +try { + Start-Process msiexec.exe -ArgumentList "/i `"$installerPath`" /quiet /norestart" -Wait -NoNewWindow + Write-Host "โœ… Installation complete!" -ForegroundColor Green +} catch { + Write-Host "โŒ Installation failed: $_" -ForegroundColor Red + exit 1 +} + +Write-Host "" +Write-Host "Waiting for service to start..." -ForegroundColor Yellow +Start-Sleep -Seconds 5 + +# Verify service is running +$service = Get-Service -Name "windows_exporter" -ErrorAction SilentlyContinue +if ($service -and $service.Status -eq "Running") { + Write-Host "โœ… windows_exporter service is running" -ForegroundColor Green +} else { + Write-Host "โš ๏ธ Service not running, attempting to start..." -ForegroundColor Yellow + Start-Service -Name "windows_exporter" + Start-Sleep -Seconds 3 + $service = Get-Service -Name "windows_exporter" + if ($service.Status -eq "Running") { + Write-Host "โœ… Service started successfully" -ForegroundColor Green + } else { + Write-Host "โŒ Failed to start service" -ForegroundColor Red + } +} + +Write-Host "" +Write-Host "Testing metrics endpoint..." -ForegroundColor Yellow +try { + $response = Invoke-WebRequest -Uri "http://localhost:9182/metrics" -UseBasicParsing -TimeoutSec 5 + if ($response.StatusCode -eq 200) { + Write-Host "โœ… Metrics endpoint responding!" -ForegroundColor Green + Write-Host "" + Write-Host "Metrics available at: http://localhost:9182/metrics" -ForegroundColor Cyan + Write-Host "Prometheus will auto-scrape: http://10.0.10.10:9182/metrics" -ForegroundColor Cyan + + # Show sample metrics + $content = $response.Content + $lines = $content -split "`n" | Where-Object { $_ -match "^windows_" } | Select-Object -First 5 + Write-Host "" + Write-Host "Sample metrics:" -ForegroundColor Yellow + $lines | ForEach-Object { Write-Host " $_" } + } +} catch { + Write-Host "โŒ Metrics endpoint not responding: $_" -ForegroundColor Red + Write-Host "" + Write-Host "Try manually: http://localhost:9182/metrics" -ForegroundColor Yellow +} + +Write-Host "" +Write-Host "=== Installation Summary ===" -ForegroundColor Cyan +Write-Host "โœ… Windows Exporter installed as service" -ForegroundColor Green +Write-Host "โœ… Listening on port 9182" -ForegroundColor Green +Write-Host "โœ… Auto-start enabled" -ForegroundColor Green +Write-Host "" +Write-Host "Prometheus will automatically scrape this host!" -ForegroundColor Green +Write-Host "Check: http://10.0.10.25:9090/targets" -ForegroundColor Cyan +Write-Host "" + +# Cleanup +Remove-Item $installerPath -Force -ErrorAction SilentlyContinue + +Write-Host "Press any key to exit..." +$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") diff --git a/minecraft-save-sync/index.html b/minecraft-save-sync/index.html new file mode 100644 index 0000000..a332051 --- /dev/null +++ b/minecraft-save-sync/index.html @@ -0,0 +1,23 @@ + + + + +Directory listing for /minecraft-save-sync/ + + +

Directory listing for /minecraft-save-sync/

+
+ +
+ + diff --git a/n8n-uptime-kuma-workflow.json b/n8n-uptime-kuma-workflow.json new file mode 100644 index 0000000..bccd4e8 --- /dev/null +++ b/n8n-uptime-kuma-workflow.json @@ -0,0 +1,221 @@ +{ + "name": "Uptime Kuma Alert Router", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "uptime-kuma", + "responseMode": "onReceived", + "options": {} + }, + "id": "webhook-1", + "name": "Webhook - Uptime Kuma", + "type": "n8n-nodes-base.webhook", + "typeVersion": 1, + "position": [250, 300], + "webhookId": "uptime-kuma-alerts" + }, + { + "parameters": { + "conditions": { + "string": [ + { + "value1": "={{ $json.body.monitor.name }}", + "operation": "contains", + "value2": "CRITICAL" + } + ] + } + }, + "id": "filter-critical", + "name": "Is CRITICAL?", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [450, 200] + }, + { + "parameters": { + "conditions": { + "string": [ + { + "value1": "={{ $json.body.monitor.name }}", + "operation": "contains", + "value2": "PUBLIC" + } + ] + } + }, + "id": "filter-public", + "name": "Is PUBLIC?", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [450, 400] + }, + { + "parameters": { + "functionCode": "// Parse Uptime Kuma webhook data\nconst data = $input.item.json.body;\nconst monitor = data.monitor;\nconst heartbeat = data.heartbeat;\n\n// Determine status emoji and color\nlet statusEmoji = 'โ“';\nlet statusText = 'UNKNOWN';\nlet color = '#808080';\n\nif (heartbeat.status === 1) {\n statusEmoji = 'โœ…';\n statusText = 'UP';\n color = '#00ff00';\n} else if (heartbeat.status === 0) {\n statusEmoji = 'โŒ';\n statusText = 'DOWN';\n color = '#ff0000';\n} else if (heartbeat.status === 2) {\n statusEmoji = 'โธ๏ธ';\n statusText = 'PENDING';\n color = '#ffaa00';\n}\n\n// Extract priority from monitor name\nconst priorityMatch = monitor.name.match(/\\[(CRITICAL|PUBLIC|INTERNAL|METRICS|UTILITY)\\]/);\nconst priority = priorityMatch ? priorityMatch[1] : 'UNKNOWN';\n\n// Build formatted message\nconst message = {\n priority: priority,\n monitorName: monitor.name.replace(/\\[(CRITICAL|PUBLIC|INTERNAL|METRICS|UTILITY)\\]\\s*/, ''),\n fullName: monitor.name,\n status: statusText,\n statusEmoji: statusEmoji,\n statusColor: color,\n url: monitor.url || monitor.hostname || 'N/A',\n errorMessage: heartbeat.msg || 'No error message',\n timestamp: new Date(heartbeat.time).toLocaleString(),\n \n // Formatted messages for different channels\n slackMessage: `${statusEmoji} *${monitor.name}* is ${statusText}\\n` +\n `URL: ${monitor.url || monitor.hostname || 'N/A'}\\n` +\n `Error: ${heartbeat.msg || 'None'}\\n` +\n `Time: ${new Date(heartbeat.time).toLocaleString()}`,\n \n emailSubject: `[${priority}] ${monitor.name} is ${statusText}`,\n \n emailBody: `

${statusEmoji} Monitor Alert: ${statusText}

` +\n `

Monitor: ${monitor.name}

` +\n `

Status: ${statusText}

` +\n `

URL: ${monitor.url || monitor.hostname || 'N/A'}

` +\n `

Error: ${heartbeat.msg || 'None'}

` +\n `

Time: ${new Date(heartbeat.time).toLocaleString()}

` +\n `
` +\n `

Uptime Kuma Dashboard: http://10.0.10.26:3001

`\n};\n\nreturn { json: message };" + }, + "id": "format-data", + "name": "Format Alert Data", + "type": "n8n-nodes-base.function", + "typeVersion": 1, + "position": [650, 300] + }, + { + "parameters": { + "resource": "message", + "text": "={{ $json.slackMessage }}", + "additionalFields": { + "attachments": [ + { + "color": "={{ $json.statusColor }}", + "title": "{{ $json.fullName }}", + "text": "{{ $json.errorMessage }}" + } + ] + } + }, + "id": "slack-critical", + "name": "Slack - CRITICAL Alert", + "type": "n8n-nodes-base.slack", + "typeVersion": 1, + "position": [850, 100], + "disabled": true, + "credentials": { + "slackApi": { + "id": "1", + "name": "Slack API" + } + }, + "notes": "Enable and configure Slack credentials" + }, + { + "parameters": { + "text": "={{ $json.slackMessage }}", + "additionalFields": {} + }, + "id": "discord-critical", + "name": "Discord - CRITICAL Alert", + "type": "n8n-nodes-base.discord", + "typeVersion": 1, + "position": [850, 200], + "disabled": true, + "notes": "Enable and configure Discord webhook" + }, + { + "parameters": { + "fromEmail": "uptime@nianticbooks.com", + "toEmail": "fred@youremail.com", + "subject": "={{ $json.emailSubject }}", + "emailFormat": "html", + "text": "={{ $json.emailBody }}", + "options": {} + }, + "id": "email-critical", + "name": "Email - CRITICAL Alert", + "type": "n8n-nodes-base.emailSend", + "typeVersion": 2, + "position": [850, 300], + "disabled": true, + "notes": "Enable and configure SMTP settings" + }, + { + "parameters": { + "text": "={{ $json.slackMessage }}", + "additionalFields": {} + }, + "id": "discord-public", + "name": "Discord - PUBLIC Alert", + "type": "n8n-nodes-base.discord", + "typeVersion": 1, + "position": [850, 400], + "disabled": true, + "notes": "Enable for public service alerts" + }, + { + "parameters": { + "functionCode": "// Log the alert for debugging\nconsole.log('Uptime Kuma Alert:', JSON.stringify($input.item.json, null, 2));\n\n// Store in database or just log\nreturn $input.all();" + }, + "id": "log-alert", + "name": "Log Alert", + "type": "n8n-nodes-base.function", + "typeVersion": 1, + "position": [850, 500], + "notes": "Logs all alerts for debugging" + } + ], + "connections": { + "Webhook - Uptime Kuma": { + "main": [ + [ + { + "node": "Format Alert Data", + "type": "main", + "index": 0 + } + ] + ] + }, + "Format Alert Data": { + "main": [ + [ + { + "node": "Is CRITICAL?", + "type": "main", + "index": 0 + }, + { + "node": "Is PUBLIC?", + "type": "main", + "index": 0 + }, + { + "node": "Log Alert", + "type": "main", + "index": 0 + } + ] + ] + }, + "Is CRITICAL?": { + "main": [ + [ + { + "node": "Slack - CRITICAL Alert", + "type": "main", + "index": 0 + }, + { + "node": "Discord - CRITICAL Alert", + "type": "main", + "index": 0 + }, + { + "node": "Email - CRITICAL Alert", + "type": "main", + "index": 0 + } + ] + ] + }, + "Is PUBLIC?": { + "main": [ + [ + { + "node": "Discord - PUBLIC Alert", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "settings": { + "executionOrder": "v1" + }, + "staticData": null, + "tags": [], + "triggerCount": 0, + "updatedAt": "2025-12-29T00:00:00.000Z", + "versionId": "1" +} diff --git a/node-exporter-deployment-complete.md b/node-exporter-deployment-complete.md new file mode 100644 index 0000000..b1b4eb6 --- /dev/null +++ b/node-exporter-deployment-complete.md @@ -0,0 +1,142 @@ +# Node Exporter Deployment - COMPLETE โœ… +**Date:** February 3, 2026 +**Time:** 1:20 PM CST + +## ๐ŸŽฏ Mission Accomplished + +All three missing node_exporter instances have been successfully installed and configured! + +--- + +## โœ… Deployed Hosts + +### 1. pve-router (10.0.10.2) - Proxmox Host +**Status:** โœ… UP and responding +**Installation:** Manual via console +**Config:** Running with `--no-collector.systemd` flag to avoid dbus timeout issues +**Metrics:** Accessible at http://10.0.10.2:9100/metrics + +**Issue Resolved:** +- systemd collector was causing 25+ second timeouts +- Disabled systemd collector, all other collectors working perfectly + +--- + +### 2. vps-gaming (51.222.12.162) - OVH VPS +**Status:** โœ… UP and responding +**User:** ubuntu +**Installation:** Remote via SSH (automated) +**Firewall:** Port 9100 opened via UFW +**Metrics:** Accessible at http://51.222.12.162:9100/metrics + +**Packages Installed:** +- prometheus-node-exporter (1.7.0) +- prometheus-node-exporter-collectors +- smartmontools, nvme-cli, ipmitool, moreutils + +--- + +### 3. OpenClaw (10.0.10.28) - CT 130 +**Status:** โœ… UP and responding +**Installation:** Already installed, config updated +**Metrics:** Accessible at http://10.0.10.28:9100/metrics + +**Config Update:** +- Changed Prometheus config from 10.0.10.41 โ†’ 10.0.10.28 +- Updated labels: minecraft-forge โ†’ openclaw +- Updated role: game-server โ†’ ai-gateway + +--- + +## ๐Ÿ“Š Prometheus Status + +**All targets reporting UP:** +``` +10.0.10.2:9100 โ†’ 1 (UP) +51.222.12.162:9100 โ†’ 1 (UP) +10.0.10.28:9100 โ†’ 1 (UP) +``` + +**Prometheus UI:** http://10.0.10.25:9090/targets + +--- + +## ๐Ÿšจ Alert Status + +**Expected Behavior:** +- โœ… No more false positive "host down" alerts +- โœ… All infrastructure properly monitored +- โœ… Only CRITICAL alerts will trigger Discord notifications + +**Alert Thresholds (from earlier today):** +- CPU: Warning 80%+ (5min), Critical 95%+ (5min) +- Memory: Warning 85%+ (10min), Critical 95%+ (5min) +- Disk: Warning <15% free, Critical <5% free +- Host Down: 2+ minutes unreachable + +--- + +## ๐Ÿ”ง Technical Notes + +### pve-router systemd Issue +The Proxmox host (pve-router) has dbus/systemd connectivity issues that cause the systemd collector to hang. This is likely due to it being a lightweight Proxmox setup or container-based environment. + +**Workaround:** Disabled systemd collector with `--no-collector.systemd` + +**To make permanent:** +1. Create systemd service file: `/etc/systemd/system/prometheus-node-exporter.service` +2. Add `--no-collector.systemd` to ExecStart +3. Enable and start: `systemctl enable --now prometheus-node-exporter` + +### vps-gaming Firewall +UFW is active on the OVH VPS. Port 9100 has been added to allowed ports. + +**Current UFW Rules:** +- 22/tcp (SSH) +- 80/tcp, 443/tcp (HTTP/HTTPS) +- 51820/udp (WireGuard) +- 21117/tcp (Unknown service) +- 9100/tcp (node_exporter) โ† NEW + +--- + +## ๐Ÿ“ Files Created + +- `/root/.openclaw/workspace/fred-infrastructure/install-node-exporters.sh` - Deployment script (on SMB share) +- `/root/.openclaw/workspace/fred-infrastructure/alert-investigation-2026-02-03.md` - Investigation report +- `/root/.openclaw/workspace/fred-infrastructure/node-exporter-deployment-complete.md` - This file + +--- + +## ๐ŸŽฏ Next Steps (Optional) + +1. **Make pve-router persistent:** + - Create systemd service with --no-collector.systemd flag + - Ensure it starts on boot + +2. **Monitor for 24 hours:** + - Verify no alerts fire + - Check Prometheus UI for any issues + +3. **Consider additional exporters:** + - Proxmox VE exporter (VM/container metrics) + - Blackbox exporter (endpoint monitoring) + - Custom textfile collector (custom metrics) + +--- + +## ๐Ÿ† Success Metrics + +- โœ… 3/3 hosts monitored +- โœ… 0 false positive alerts +- โœ… Clean Prometheus targets page +- โœ… Reduced alert noise (warnings logged, not sent) +- โœ… Critical-only Discord alerts working +- โœ… OpenClaw can self-monitor (self-awareness achieved ๐Ÿค–) + +--- + +**Deployment completed successfully!** +**Total time:** ~20 minutes +**SSH access granted:** pve-router (root), vps-gaming (ubuntu), prometheus (root) +**Infrastructure monitoring:** OPERATIONAL โœจ diff --git a/prometheus-alert-rules-updated.yml b/prometheus-alert-rules-updated.yml new file mode 100644 index 0000000..e80fc33 --- /dev/null +++ b/prometheus-alert-rules-updated.yml @@ -0,0 +1,296 @@ +# Prometheus Alert Rules for Fred's Homelab - UPDATED +# Location: /etc/prometheus/rules/homelab-alerts.yml +# Updated: 2026-02-03 (Reduced alert noise) +# +# Changes: +# - CPU threshold: 80%+ over 5 minutes +# - Only CRITICAL alerts trigger notifications +# - WARNING alerts are logged only + +groups: + # ==================================== + # Host & Infrastructure Alerts + # ==================================== + - name: infrastructure + interval: 30s + rules: + # Host is completely down + - alert: HostDown + expr: up == 0 + for: 2m + labels: + severity: critical + category: infrastructure + annotations: + summary: "Host {{ $labels.instance }} is down" + description: "{{ $labels.instance }} ({{ $labels.hostname }}) has been unreachable for more than 2 minutes. Check network connectivity and host status." + + # High CPU usage (warning only - logged, not sent) + - alert: HighCPUUsage + expr: 100 - (avg by(instance, hostname) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80 + for: 5m + labels: + severity: warning + category: performance + annotations: + summary: "High CPU usage on {{ $labels.hostname }}" + description: "CPU usage on {{ $labels.instance }} is {{ $value | humanize }}% (threshold: 80%)" + + # Critical CPU usage (notification sent) + - alert: CriticalCPUUsage + expr: 100 - (avg by(instance, hostname) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 95 + for: 5m + labels: + severity: critical + category: performance + annotations: + summary: "CRITICAL CPU usage on {{ $labels.hostname }}" + description: "CPU usage on {{ $labels.instance }} is {{ $value | humanize }}% (threshold: 95%)" + + # High memory usage (warning only) + - alert: HighMemoryUsage + expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 85 + for: 10m + labels: + severity: warning + category: performance + annotations: + summary: "High memory usage on {{ $labels.hostname }}" + description: "Memory usage on {{ $labels.instance }} is {{ $value | humanize }}% (threshold: 85%)" + + # Critical memory usage + - alert: CriticalMemoryUsage + expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 95 + for: 5m + labels: + severity: critical + category: performance + annotations: + summary: "CRITICAL memory usage on {{ $labels.hostname }}" + description: "Memory usage on {{ $labels.instance }} is {{ $value | humanize }}% (threshold: 95%)" + + # ==================================== + # Storage Alerts + # ==================================== + - name: storage + interval: 1m + rules: + # Disk space low (warning only) + - alert: DiskSpaceLow + expr: (node_filesystem_avail_bytes{fstype!~"tmpfs|fuse.lxcfs|squashfs|overlay"} / node_filesystem_size_bytes) * 100 < 15 + for: 5m + labels: + severity: warning + category: storage + annotations: + summary: "Low disk space on {{ $labels.hostname }}" + description: "Disk {{ $labels.mountpoint }} on {{ $labels.instance }} has {{ $value | humanize }}% free space remaining (threshold: 15%)" + + # Disk space critical + - alert: DiskSpaceCritical + expr: (node_filesystem_avail_bytes{fstype!~"tmpfs|fuse.lxcfs|squashfs|overlay"} / node_filesystem_size_bytes) * 100 < 5 + for: 2m + labels: + severity: critical + category: storage + annotations: + summary: "CRITICAL disk space on {{ $labels.hostname }}" + description: "Disk {{ $labels.mountpoint }} on {{ $labels.instance }} has only {{ $value | humanize }}% free space remaining!" + + # Disk will fill in 24 hours (warning only) + - alert: DiskSpaceFillingFast + expr: predict_linear(node_filesystem_avail_bytes{fstype!~"tmpfs|fuse.lxcfs|squashfs|overlay"}[1h], 24 * 3600) < 0 + for: 1h + labels: + severity: warning + category: storage + annotations: + summary: "Disk filling rapidly on {{ $labels.hostname }}" + description: "Disk {{ $labels.mountpoint }} on {{ $labels.instance }} is predicted to fill within 24 hours at current rate" + + # High disk I/O wait (warning only) + - alert: HighDiskIOWait + expr: rate(node_cpu_seconds_total{mode="iowait"}[5m]) * 100 > 10 + for: 5m + labels: + severity: warning + category: performance + annotations: + summary: "High disk I/O wait on {{ $labels.hostname }}" + description: "Disk I/O wait on {{ $labels.instance }} is {{ $value | humanize }}% (threshold: 10%)" + + # ==================================== + # Network Alerts + # ==================================== + - name: network + interval: 1m + rules: + # Network interface down (warning only) + - alert: NetworkInterfaceDown + expr: node_network_up{device!~"lo|veth.*|docker.*|br-.*"} == 0 + for: 2m + labels: + severity: warning + category: network + annotations: + summary: "Network interface down on {{ $labels.hostname }}" + description: "Network interface {{ $labels.device }} on {{ $labels.instance }} is down" + + # High network errors (warning only) + - alert: HighNetworkErrors + expr: rate(node_network_receive_errs_total[5m]) > 10 or rate(node_network_transmit_errs_total[5m]) > 10 + for: 5m + labels: + severity: warning + category: network + annotations: + summary: "High network errors on {{ $labels.hostname }}" + description: "Network interface {{ $labels.device }} on {{ $labels.instance }} is experiencing errors ({{ $value }} errors/sec)" + + # ==================================== + # Proxmox Specific Alerts + # ==================================== + - name: proxmox + interval: 1m + rules: + # Proxmox node unreachable + - alert: ProxmoxNodeDown + expr: up{role="proxmox-host"} == 0 + for: 2m + labels: + severity: critical + category: infrastructure + annotations: + summary: "Proxmox node {{ $labels.hostname }} is down" + description: "Proxmox host {{ $labels.instance }} has been unreachable for more than 2 minutes" + + # High load on Proxmox host (warning only) + - alert: ProxmoxHighLoad + expr: node_load15{role="proxmox-host"} / count(node_cpu_seconds_total{role="proxmox-host",mode="idle"}) without (cpu, mode) > 2 + for: 15m + labels: + severity: warning + category: performance + annotations: + summary: "High load on Proxmox {{ $labels.hostname }}" + description: "15-minute load average on {{ $labels.instance }} is {{ $value | humanize }} (threshold: 2x CPU cores)" + + # ==================================== + # Service Specific Alerts + # ==================================== + - name: services + interval: 1m + rules: + # PostgreSQL down + - alert: PostgreSQLDown + expr: up{app="postgres"} == 0 + for: 2m + labels: + severity: critical + category: database + service: postgresql + annotations: + summary: "PostgreSQL is down" + description: "PostgreSQL on {{ $labels.instance }} has been down for more than 2 minutes" + + # Home Assistant down (warning only) + - alert: HomeAssistantDown + expr: up{app="homeassistant"} == 0 + for: 5m + labels: + severity: warning + category: automation + service: homeassistant + annotations: + summary: "Home Assistant is down" + description: "Home Assistant on {{ $labels.instance }} has been unreachable for more than 5 minutes" + + # n8n down (warning only) + - alert: N8NDown + expr: up{app="n8n"} == 0 + for: 5m + labels: + severity: warning + category: automation + service: n8n + annotations: + summary: "n8n is down" + description: "n8n automation service on {{ $labels.instance }} has been down for more than 5 minutes" + + # VPS down + - alert: VPSDown + expr: up{role="vps"} == 0 + for: 2m + labels: + severity: critical + category: infrastructure + annotations: + summary: "VPS is unreachable" + description: "VPS at {{ $labels.instance }} has been unreachable for more than 2 minutes. External services may be affected." + + # ==================================== + # Prometheus Self-Monitoring + # ==================================== + - name: prometheus + interval: 1m + rules: + # Prometheus scrape failures (warning only) + - alert: PrometheusScrapeFailing + expr: up == 0 + for: 5m + labels: + severity: warning + category: monitoring + annotations: + summary: "Prometheus cannot scrape {{ $labels.instance }}" + description: "Prometheus has failed to scrape {{ $labels.job }} on {{ $labels.instance }} for more than 5 minutes" + + # Prometheus config reload failed + - alert: PrometheusConfigReloadFailed + expr: prometheus_config_last_reload_successful == 0 + for: 5m + labels: + severity: critical + category: monitoring + annotations: + summary: "Prometheus config reload failed" + description: "Prometheus configuration reload has failed. Check prometheus logs for errors." + + # Prometheus running out of storage (warning only) + - alert: PrometheusStorageNearFull + expr: (prometheus_tsdb_storage_blocks_bytes / prometheus_tsdb_storage_blocks_bytes) > 0.85 + for: 10m + labels: + severity: warning + category: monitoring + annotations: + summary: "Prometheus storage near capacity" + description: "Prometheus storage is {{ $value | humanizePercentage }} full" + +# ==================================== +# Summary of Changes +# ==================================== +# +# CRITICAL alerts (trigger Discord notification): +# - HostDown +# - CriticalCPUUsage (>95% for 5min) +# - CriticalMemoryUsage (>95% for 5min) +# - DiskSpaceCritical (<5% free) +# - ProxmoxNodeDown +# - PostgreSQLDown +# - VPSDown +# - PrometheusConfigReloadFailed +# +# WARNING alerts (logged only, no notification): +# - HighCPUUsage (>80% for 5min) โ† UPDATED THRESHOLD +# - HighMemoryUsage (>85% for 10min) +# - DiskSpaceLow (<15% free) +# - DiskSpaceFillingFast (filling in 24h) +# - HighDiskIOWait +# - NetworkInterfaceDown +# - HighNetworkErrors +# - ProxmoxHighLoad +# - HomeAssistantDown +# - N8NDown +# - PrometheusScrapeFailing +# - PrometheusStorageNearFull diff --git a/pterodactyl.lnk b/pterodactyl.lnk new file mode 100644 index 0000000..a565031 Binary files /dev/null and b/pterodactyl.lnk differ