Skip to content

chore(ansible): cluster-wide Tailscale-first SSH + Claude Code permission sync#144

Open
Mikecranesync wants to merge 6 commits into
mainfrom
chore/ansible-cluster-ssh-claude-sync
Open

chore(ansible): cluster-wide Tailscale-first SSH + Claude Code permission sync#144
Mikecranesync wants to merge 6 commits into
mainfrom
chore/ansible-cluster-ssh-claude-sync

Conversation

@Mikecranesync
Copy link
Copy Markdown
Owner

Summary

Makes ~/.ssh/config and ~/.claude/settings.json fleet-canonical across all 3 Mac minis via Ansible, so every node — and therefore every repo on every node — gets the same Tailscale-first SSH aliases and the same Claude Code permission allow-list. No more per-repo config.

What changed

File Change
infra/ansible/templates/ssh_config.j2 NEW — Tailscale-first SSH config (alpha/bravo/charlie/plc/travel/prod/pi aliases + global ControlMaster + LAN fallbacks)
infra/ansible/files/merge_claude_permissions.py NEW — idempotent JSON merger; adds canonical Bash(ssh *), Bash(scp *), Bash(rsync *), Bash(tailscale *), Bash(nc -z *), Bash(ping -c* *), Bash(dig *), Bash(host *) entries into each node's ~/.claude/settings.json; version-marked so safe to re-run
infra/ansible/playbook.yml +4 tagged tasks (ssh_claude_sync, ssh, claude_perms)
infra/ansible/inventory.ini Refreshed stale Tailscale IPs for alpha/charlie (old values were retired hardware, offline 41–54d)
infra/ansible/README.md Documents both new capabilities + updated alias table

Why (answers "will MIRA and other repos get this?")

  • ~/.claude/settings.json is user-global per Mac → applies to every repo on that Mac.
  • ~/.ssh/config is user-global per Mac → same.
  • Running ansible-playbook --tags ssh_claude_sync brings every Mac in the inventory to the same state in one shot. Future Macs just get added to inventory.ini.

Rollout commands

# Safe dry-run (no changes)
ansible-playbook -i inventory.ini playbook.yml --tags ssh_claude_sync --check

# Apply the new tasks only (skips brew bundle / sudo / pip)
ansible-playbook -i inventory.ini playbook.yml --tags ssh_claude_sync

Test plan

  • YAML parses (1 play, 17 tasks, ansible-playbook --syntax-check clean)
  • Python py_compile on merger script
  • Jinja2 template renders (2,586 chars, canonical IPs present)
  • Merger dry-run against a copy of charlie's settings.json: 9/9 canonical entries added, all existing keys preserved
  • ansible-playbook --tags ssh_claude_sync --check --limit charlie — dry-run against CHARLIE
  • ansible-playbook --tags ssh_claude_sync --limit charlie — apply to CHARLIE
  • Fleet-wide dry-run + apply against alpha + bravo

Roster

Tracked in /Users/Shared/cluster/betterclaw/memory/automated-agents-roster.md.

🤖 Generated with Claude Code

CharlieNode and others added 6 commits April 16, 2026 00:51
Adds OEE computation engine, 60-second tick loop, fleet endpoints:
- compute_oee() pure function: A×P×Q with clamp/rounding (PRD §10 AC#3, AC#5)
- 30-tick alert at OEE < 60% per line (OEE_ALERT_TICKS=30)
- ItemCount delta tracking from HR100 (cumulative register)
- run_time_in_window() SQL: sums RUNNING seconds in last tick window
- 4 new endpoints: /oee, /oee/history, /oee/summary, /kpis
- oee_calculator background task wired into FastAPI lifespan
- 16 new tests, zero regressions (48/48 suite)

Closes MIRA#321

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
…sts, 66 total pass)

Work order CRUD + product management:
- POST/GET /api/mes/products — create SKU with ideal_cycle_sec
- POST /api/mes/work-orders — creates PENDING; validates line + product + unique order_number
- GET /api/mes/work-orders — list with ?line_id= / ?status= filters
- GET /api/mes/work-orders/{id} — detail
- PATCH /api/mes/work-orders/{id}/status — enforced transitions (PENDING→ACTIVE→COMPLETE/CANCELLED)
- 409 when second ACTIVE work order attempted on same line

Schedule-aware TEEP:
- compute_oee() gains utilisation param (default 1.0 — Week 3 backward compat)
- TEEP = OEE × utilisation (clamped 0-1)
- _active_utilisation() queries schedules table; falls back to 1.0 when no schedules exist

18 new tests, zero regressions (66/66 suite)
Closes MIRA#322

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
…(29 tests, 95 total pass)

Three downtime capture modes now live:
- AUTO: PLC fault code → reason_code already wired via state_poller (Weeks 2+)
- MANUAL: POST /api/mes/lines/{id}/downtime with direct reason_code (OPERATOR/PLC)
- NLP: POST with free-text description → keyword classifier → reason_code (MIRA_AI)

New:
- downtime_classifier.py: pure keyword→reason_code with 13-rule priority table
  (E_STOP > MAINT_PM > MAINT_BREAKDOWN > CHANGEOVER > JAM > ... > UNKNOWN fallback)
- GET /api/mes/downtime-reasons — list all 14 seeded codes
- GET /api/mes/lines/{id}/downtime?hours=8 — downtime history with duration_min
- POST /api/mes/lines/{id}/downtime — attach/override reason on open DOWN event
  409 if line has no open DOWN/CHANGEOVER event
  422 if reason_code unknown or neither field provided

29 new tests (20 NLP classifier + 9 API), zero regressions (95/95 suite)
Closes MIRA#323

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
…l pass)

Bidirectional work order sync between MES and CMMS via GitHub Gist:

Outbound (MES → CMMS):
- POST /api/mes/cmms/sync/{id} — pushes WO as Gist (Markdown+CSV), saves cmms_ref
- GET  /api/mes/cmms/sync/{id} — returns sync status (synced bool, gist_id, ts)
- Idempotent: repeat calls update the existing Gist, never create duplicates
- cmms_enabled=False (default) → synthetic response, no HTTP calls (safe in tests)

Inbound (CMMS → MES):
- POST /api/mes/cmms/ingest — resolves product by SKU + line by name, creates PENDING WO
- Idempotent: duplicate order_number returns existing WO (201)

Infrastructure:
- Migration 0002: adds cmms_ref TEXT + cmms_synced_at TIMESTAMPTZ to work_orders
- WorkOrder ORM updated with cmms_ref + cmms_synced_at columns
- config: cmms_enabled bool + cmms_github_token (Doppler-managed, never logged)
- cmms_client.format_work_order() pure mapping function; 5 pure-function tests

17 new tests, zero regressions (112/112 suite)
Closes MIRA#324

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
…sion sync

Makes `~/.ssh/config` and `~/.claude/settings.json` fleet-canonical across all
Mac minis, so every node (and therefore every repo on every node) gets:

- Tailscale-first SSH aliases: alpha, bravo (+ bravo-lan fallback), charlie,
  plc, travel, prod (100.68.120.99 VPS + prod-public fallback to 165.245.138.91),
  pi. Global ServerAliveInterval + ControlMaster for warm connections.

- Claude Code permission allow-list canonical entries (ssh/scp/rsync/tailscale/
  nc/ping/dig/host) merged additively into each node's settings.json so Claude
  Code sessions stop prompting for cluster-internal commands.

Additions:
- templates/ssh_config.j2     — Jinja2 template, user-home parameterized
- files/merge_claude_permissions.py — idempotent JSON merger, version-marked
- playbook.yml                — 4 new tasks appended (after Claude dir task)
- inventory.ini               — refreshed stale Tailscale IPs for alpha/charlie
                                (old values pointed at retired hardware)
- README.md                   — documents both new capabilities

Validated locally:
- YAML parse: 1 play, 17 tasks
- Python py_compile: clean
- Jinja2 template render: 2586 chars, canonical IPs present
- Merge script dry-run against a copy of charlie's settings.json: added 9/9
  canonical entries, preserved all existing keys

Not run yet against remote hosts — this commit is infrastructure-only.
Next step: review, push, and `ansible-playbook -i inventory.ini playbook.yml`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds tags [ssh_claude_sync, ssh, claude_perms] to the 4 new tasks so they
can be rolled out without re-running the heavier fleet-sync (brew bundle,
NOPASSWD sudo, pip install, etc.):

    ansible-playbook -i inventory.ini playbook.yml --tags ssh_claude_sync

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant