Pulls the deploy infra forward from §10 so m can see slice 1 on his LAN.
- Dockerfile: multi-stage golang:1.25-alpine → distroless/static-debian12.
CGO_ENABLED=0 (modernc.org/sqlite is pure Go). USER 1000:1000 so the
bind-mount on mDock (owned by m:m) is writable without chowning the
host dir. -trimpath + -s -w; 12.2MB final image.
- docker-compose.yml: matches the mDock convention surveyed earlier
(container_name explicit, restart: unless-stopped, env_file in
/home/m/secrets/mcables/.env, bind-mount /home/m/stacks/mcables/data,
port 7777 exposed on LAN). Image temporarily under the mai/ namespace
on mgit.msbls.de because mAi doesn't have write access to m/* today —
documented in a comment so retagging is one line when permissions land.
- .dockerignore: keeps .git, .worktrees, .m, data/, docs/, *.md,
editor cruft out of the build context.
Manual deploy verified end-to-end:
- docker build → image sha256:76624f17 (12.2MB)
- mAi-authenticated push to mgit.msbls.de/mai/mcables:latest
- ssh mdock anonymous pull works (registry allows public reads on this
namespace)
- POST /api/projects {"name":"LOFT"} returns the row, GET /api/projects
shows it; docker compose restart preserves it on disk; second GET
still shows LOFT.
Gitea Actions auto-deploy left for a follow-up task per the head's
instruction — gets us the moving parts right first.
156 lines
5.1 KiB
Markdown
156 lines
5.1 KiB
Markdown
# 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
|
|
|
|
```sh
|
|
go run ./cmd/mcables
|
|
# open http://localhost:7777
|
|
```
|
|
|
|
Or built:
|
|
|
|
```sh
|
|
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` | (unset) | Used by slice 5 export — not consumed yet. |
|
|
| `MEXDRAW_TOKEN` | (unset) | Bearer for the mExDraw export. Not consumed yet. |
|
|
|
|
### Tests
|
|
|
|
```sh
|
|
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/`):
|
|
|
|
```sh
|
|
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):
|
|
|
|
```sh
|
|
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**:
|
|
|
|
```sh
|
|
ssh mdock 'cd /home/m/stacks/mcables && docker compose pull && docker compose up -d'
|
|
```
|
|
|
|
4. **Verify** from any LAN host:
|
|
|
|
```sh
|
|
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.
|