Compare commits

...

2 Commits

Author SHA1 Message Date
mAi
8a31f0af60 deploy: Dockerfile + docker-compose.yml for mDock, manual first roll
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.
2026-05-15 18:01:30 +02:00
mAi
98f30306a1 merge: slice 1 — bootstrap + project CRUD + global cable_types
picasso shipped (7 commits @ 905c75c):
- Go module + cmd/mcables binary
- internal/db: migrations runner + 001_init.sql (full v3 schema, 5 cable_types seeded)
- internal/db/store.go: projects + cable_types CRUD with sentinel errors
- internal/server: net/http handlers (Go 1.22 ServeMux)
- web/static: project picker, legend, modals (new project / cable type / delete), ?project= URL state
- 17 store tests green, end-to-end smoke verified

Endpoints live: /api/healthz, /api/projects {GET POST}, /api/projects/:id
{GET PATCH DELETE?confirm=<name>}, /api/cable-types {GET POST}, /api/cable-types/:id {PATCH DELETE}.

Next: slice 2 (frames + devices + drag-to-position) on m's go.
2026-05-15 16:50:02 +02:00
4 changed files with 155 additions and 0 deletions

32
.dockerignore Normal file
View File

@@ -0,0 +1,32 @@
# Source-control + worktree noise
.git
.gitignore
.gitea
.worktrees
# mai worker-local logs
.m
# Local runtime state (mounted as a volume in production)
data
*.db
*.db-wal
*.db-shm
# Build artefacts
bin
mcables
# Editor cruft
.vscode
.idea
*.swp
# Documentation (lives in git, not in the image)
docs
CLAUDE.md
README.md
# Test files (build still respects them via go.mod, this only strips
# the test fixtures we might check in later)
**/testdata

36
Dockerfile Normal file
View File

@@ -0,0 +1,36 @@
# syntax=docker/dockerfile:1.7
#
# mCables — single-stage build → distroless runtime image.
# go.mod requires go 1.25; modernc.org/sqlite is pure Go so CGO_ENABLED=0
# and a distroless/static runtime is all we need.
FROM golang:1.25-alpine AS build
WORKDIR /src
# Cache deps before copying the rest of the source.
COPY go.mod go.sum ./
RUN go mod download
COPY . .
# -trimpath strips local paths from the binary; -s -w drops debug info.
RUN CGO_ENABLED=0 GOOS=linux go build \
-trimpath \
-ldflags="-s -w" \
-o /out/mcables \
./cmd/mcables
FROM gcr.io/distroless/static-debian12:nonroot
WORKDIR /app
COPY --from=build /out/mcables /app/mcables
ENV MCABLES_ADDR=0.0.0.0:7777 \
MCABLES_DB=/app/data/mcables.db
EXPOSE 7777
# Run as UID:GID 1000:1000 to match m on mDock — the bind-mounted
# /home/m/stacks/mcables/data is owned by m:m, so the container can write
# to it without chowning the host dir. distroless/static-debian12 accepts
# arbitrary numeric UIDs; the Go binary doesn't need a /etc/passwd entry.
USER 1000:1000
ENTRYPOINT ["/app/mcables"]

View File

@@ -75,6 +75,68 @@ 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

25
docker-compose.yml Normal file
View File

@@ -0,0 +1,25 @@
# mCables — production compose for mDock.
# Lives at /home/m/stacks/mcables/docker-compose.yml on mDock.
# Matches the existing mDock service patterns (mgreen, mgeo, …).
services:
mcables:
# Pushed under the mAi namespace because the mAi token doesn't have
# write permission to the m/* namespace on mgit.msbls.de today. If m
# later grants mAi collaborator access on m/mCables, retag to
# mgit.msbls.de/m/mcables:latest and align with the other mDock
# services (msports-garmin, mgreen-journal, …).
image: mgit.msbls.de/mai/mcables:latest
container_name: mcables
restart: unless-stopped
ports:
- "7777:7777"
environment:
- TZ=Europe/Berlin
- MCABLES_ADDR=0.0.0.0:7777
- MCABLES_DB=/app/data/mcables.db
env_file:
# Empty for slice 1. MEXDRAW_TOKEN lands here when slice 5 ships.
- /home/m/secrets/mcables/.env
volumes:
- /home/m/stacks/mcables/data:/app/data