mAi 4202d0465f feat(v5 slice 1): clamps schema + store helpers + snapshot
Migration 007 introduces the v5 routing primitive:
- clamps table (project-scoped, optional frame_id, excalidraw_id).
- cable_clamps join (cable_id, clamp_id, ord) with PK on (cable_id, ord)
  and UNIQUE (cable_id, clamp_id) to block a clamp visiting the same
  cable twice.

Store helpers in internal/db/clamps.go:
- CreateClamp / GetClamp / ListClamps / UpdateClamp / DeleteClamp —
  standard project-scoped CRUD. UpdateClamp uses FrameRef tri-state.
- AttachClampToCable — appends or inserts at a given ord. Mid-sequence
  inserts use a two-pass shift (bump by 10000, settle to ord+1) since
  SQLite UPDATE doesn't support ORDER BY and a single bulk +1 would
  collide with the UNIQUE (cable_id, ord) PK.
- DetachClampFromCable — removes the row then closes the gap.
- ReorderCableClamps — replaces the whole sequence in one tx.
- ListClampsForCable / ListCableClamps — read helpers.

Snapshot now carries clamps + cable_clamps arrays so the frontend can
hydrate everything in one call.

Tests cover create / update / cascade-delete / attach (append + insert
+ duplicate-rejected) / detach (gap closes) / reorder / snapshot.
2026-05-16 13:40:53 +02:00

mCables

Cable-management framework for m's setup — visual web editor backed by a single Go binary + SQLite, generating Excalidraw drawings via mExDraw.

Each cable-managed environment (LOFT, OFFICE, …) is a separate mCables project; each project is backed by exactly one .excalidraw drawing on mxdrw.msbls.de.

Status

Slice 1 — bootstrap shipped. Projects + global cable types are end-to-end; the SVG canvas is intentionally empty until slice 2.

Slice What's in it Status
1 Project CRUD, global cable types, empty SVG canvas, project picker
2 Frames + devices, drag-to-position pending
3 Ports + cables (click-port → click-port) pending
4 IO markers + cable-type editing pending
5 Export to mxdrw.msbls.de pending

Run it

go run ./cmd/mcables
# open http://localhost:7777

Or built:

make build
./bin/mcables

The binary serves the frontend from an embedded web/static/ and the JSON API under /api/. SQLite lives at ./data/mcables.db by default.

Environment

Var Default Notes
MCABLES_ADDR 0.0.0.0:7777 Listen address.
MCABLES_DB ./data/mcables.db SQLite path. Parent dir is created on boot.
MEXDRAW_BASE_URL https://mxdrw.msbls.de Base URL for mExDraw export.
MEXDRAW_USER (unset) Username for the mxdrw HTTP Basic Auth on export. Required.
MEXDRAW_PASS (unset) Password for the mxdrw HTTP Basic Auth on export. Required.

Tests

make test           # go test -race ./...

Store-level tests cover projects + cable-types CRUD, the drawing_name auto-default, the ?confirm=<name> guardrail on DELETE /api/projects/:pid, and the ON DELETE RESTRICT on a referenced cable type.

API (slice 1)

GET    /api/healthz                       → 200 {"status":"ok"}

GET    /api/projects                      → [Project, …]
POST   /api/projects                      ← {name, drawing_name?, description?}
                                            drawing_name defaults to "<name>.excalidraw"
GET    /api/projects/:pid                 → {project, cable_types, frames, devices, …}
PATCH  /api/projects/:pid                 ← partial
DELETE /api/projects/:pid?confirm=<name>  ← confirm must equal current name

GET    /api/cable-types                   → [CableType, …]   (global)
POST   /api/cable-types                   ← {name, color}
PATCH  /api/cable-types/:id               ← partial — affects every project
DELETE /api/cable-types/:id               ← 409 in_use if any cable references it

Deploy to mDock

mCables runs on mDock at http://mdock:7777 as a docker-compose service under /home/m/stacks/mcables/. Pattern matches the other mDock services (mgreen-journal, mgeo, msports-garmin, …) — no Dokploy, no reverse proxy, LAN-trusted.

Manual deploy (first roll)

  1. Build + push the image (from any host with docker; today the image lives in mAi's Gitea namespace because mAi doesn't have write access to m/):

    docker build -t mgit.msbls.de/mai/mcables:latest .
    awk '/machine mgit.msbls.de/{getline; getline; print $2}' ~/.netrc-mai \
      | docker login mgit.msbls.de -u mAi --password-stdin
    docker push mgit.msbls.de/mai/mcables:latest
    
  2. Prepare directories on mDock (one-time):

    ssh mdock 'mkdir -p /home/m/stacks/mcables/data /home/m/secrets/mcables \
               && touch /home/m/secrets/mcables/.env \
               && chmod 0600 /home/m/secrets/mcables/.env'
    scp docker-compose.yml mdock:/home/m/stacks/mcables/docker-compose.yml
    
  3. Pull + start:

    ssh mdock 'cd /home/m/stacks/mcables && docker compose pull && docker compose up -d'
    
  4. Verify from any LAN host:

    curl http://mdock:7777/api/healthz       # → {"status":"ok"}
    curl http://mdock:7777/api/cable-types   # → the 5 seeded types
    

To update to a new build: rebuild + push the image, then ssh mdock 'cd /home/m/stacks/mcables && docker compose pull && docker compose up -d'.

Persistence

SQLite lives at /home/m/stacks/mcables/data/mcables.db on the host (bind-mounted into the container at /app/data). Container runs as UID 1000:1000 to align with m:m ownership on mDock — DB files end up owned by m, the host user.

docker compose restart keeps the data intact (tested 2026-05-15).

Automation — follow-up task

This first roll is manual. A Gitea Actions workflow on the self-hosted runner already on mDock (/home/m/act-runner/, label self-hosted:host) — build → push → docker compose up -d on every push to main — is a separate task per the design's §10. Tracking spawned by the head if/when wanted.

Design + project conventions

  • docs/design.md — full v3 design (schema, API, importer/export conventions, slices, mDock deploy notes).
  • CLAUDE.md — project instructions for mai workers.

Architecture

Layer Tech
DB SQLite via modernc.org/sqlite (cgo-free), WAL, FKs on
Backend Go 1.22+ net/http ServeMux pattern routing, single binary
Frontend Vanilla ES modules + SVG, no build step, embedded via embed.FS
Export (slice 5) mExDraw HTTP API on mxdrw.msbls.de

LAN-trusted, no auth.

Description
Cable management — visual interface + SQLite inventory, integrates with mExDraw for diagrams.
Readme 378 KiB
Languages
Go 58.5%
JavaScript 34.7%
CSS 4.1%
HTML 2.4%
Dockerfile 0.2%