build: Dockerfile + Dokploy manifest + README

- Multi-stage Dockerfile: golang:1.25-alpine builder → distroless static
  runtime as nonroot. Image weighs ~15 MB. Embeds templates, static
  assets and migrations into the single binary.
- deploy/dokploy.yaml documents the Dokploy app for projax.msbls.de:
  Tailscale-only, healthz path, single replica, secret PROJAX_DB_URL.
  Translates to the Dokploy UI; not auto-applied.
- README rewritten as runbook: env vars, route table, test command,
  deploy notes, trust model (Tailscale + no auth in v1, defer to
  Supabase auth if it ever outgrows the fence), schema summary.
- .dockerignore strips .git, .m, .claude, docs, tests from build ctx.
- .gitignore covers ad-hoc binary and dist artefacts.

Verified locally: docker build succeeds, container responds to /healthz
and / against msupabase via --network host.
This commit is contained in:
mAi
2026-05-15 13:26:53 +02:00
parent 9f905de461
commit 9466759aeb
5 changed files with 128 additions and 28 deletions

9
.dockerignore Normal file
View File

@@ -0,0 +1,9 @@
.git
.worktrees
.m
.claude
.mcp.json
*.md
!README.md
docs/
**/*_test.go

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/projax
/dist/
*.log
.DS_Store

15
Dockerfile Normal file
View File

@@ -0,0 +1,15 @@
# syntax=docker/dockerfile:1.6
FROM golang:1.25-alpine AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/projax ./cmd/projax
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /out/projax /projax
ENV PROJAX_LISTEN_ADDR=:8080
EXPOSE 8080
USER nonroot:nonroot
ENTRYPOINT ["/projax"]

View File

@@ -1,41 +1,78 @@
# projax
m's personal project + self-management system. The data backbone for everything m tracks — code projects, physical projects, strategic threads, life themes, daily ops. Multiple interfaces consume the same model. Otto is one of them, not the owner.
m's personal data backbone for self-management — areas of life, projects within them, and aggregated views over tasks that live elsewhere. Subsumes scattered state currently held in `mai.projects`, CalDAV task lists, Gitea issues, and mBrian topic hubs.
## Status
Spec: `docs/design.md`. Project conventions: `CLAUDE.md`.
Bootstrap. Inventor designs schema + lifecycle + interface contracts before any code lands.
## Run locally
## Vision
```
export PROJAX_DB_URL=postgres://postgres:<pw>@<msupabase-host>:6789/postgres?sslmode=disable
go run ./cmd/projax
```
- **Single source of truth** for what m is doing, has done, will do, is sitting on, is procrastinating about.
- **Multiple interfaces** (none is canonical): Otto-PWA, browser UI, Excalidraw views, CalDAV task lists, CLI is *not* a requirement (m explicitly opted out).
- **Project Code** as the lingua franca — concise, human-readable, ambiguity-free across all interfaces.
- **Subsumes today's scattered state**: `mai.projects`, Gitea issues, CalDAV tasks, mBrian topic-hubs, Dokploy services, ~/dev/CLAUDE.md files. Migration over time.
- **Goes beyond code**: physical projects (mGreen greenhouse, household), strategic threads (career positioning), life themes (sport goals, learning) — all first-class.
Defaults:
- `PROJAX_LISTEN_ADDR=:8080`
- `PROJAX_AUTO_MIGRATE=on` (set to `off` to skip on-start migration apply)
## Out of scope (for now)
Visit `http://localhost:8080/`. Routes:
- Replacing mai.projects-the-table. projax will likely *own* it instead, or sync, or supersede gradually — design pass decides.
- Multi-user. m only.
- Public exposure / sharing. Local-first, runs on m's infra.
- Becoming a generic project-management product. Built for m's stack and tone.
| Route | Purpose |
| ------------------------- | ------------------------------------------------------ |
| `GET /` | Tree of areas + projects, plus orphan mai.projects |
| `GET /i/{path}` | Item detail; editable for projax, read-only for mai |
| `POST /i/{path}` | Save edits to a projax-native item |
| `POST /i/{path}/promote` | Promote a mai.projects orphan into a projax item |
| `GET /new?parent={path}` | Create a new item (area at root, project under parent) |
| `POST /new` | Submit |
| `GET /admin/classify` | Orphan list with inline HTMX promote |
| `GET /healthz` | DB ping |
| `GET /static/style.css` | Embedded CSS |
## Architecture (TBD by inventor)
## Test
Open questions for the first design pass:
DB-backed integration tests are skipped automatically when no `PROJAX_DB_URL` / `SUPABASE_DATABASE_URL` is set:
- Schema shape: relational (Postgres? SQLite?) vs graph (mBrian-style) vs hybrid
- Project Code generation: shortcode pattern, collision rules, retro-fitting to existing 30+ projects
- Lifecycle states + transitions (idea → active → paused → done → archived?)
- How interfaces share the model — REST API? GraphQL? direct DB read?
- Where the data lives: msupabase schema? Own DB? mBrian sub-graph?
- Integration shape with: mai.* (tasks, workers, sessions), Gitea, CalDAV, mBrian, Otto-PWA, Excalidraw
- Migration: 28 active mai.projects + ~15 test rows + 4 archived — how to clean up and import
```
SUPABASE_DATABASE_URL=postgres://... go test ./...
```
## Refs
Covers: migration idempotency, path-trigger semantics (nest, rename, re-parent, cycle, structural rules), `items_unified` source split + promotion hiding, every HTTP handler, and a Promote round-trip.
- mai.projects current state: msupabase `mai.projects` table
- otto's project inventory walk-through (head session 2026-05-15)
- mBrian topic-hubs (paliad, hlckm, tech-msbls, ai, professional-positioning, schadensberechnung-lizenzanalogie, flexsiebels-brand)
- CalDAV calendars on `dav.msbls.de`: work, plan, privat, mhome, mcalendar, logm, people
## Deploy (Dokploy on mlake)
`deploy/dokploy.yaml` is a reference manifest. Translate to the Dokploy UI:
1. Create an app `projax` with `Dockerfile` build context = repo root.
2. Set domain `projax.msbls.de` (Tailscale-only — do **not** publish through public reverse proxy).
3. Secret `PROJAX_DB_URL` pointing at msupabase's Tailscale address on port 6789 with the `postgres` user.
4. Health check path `/healthz`.
5. Single replica.
The image is a distroless static container running as `nonroot`. Total image size is well under 20 MiB because everything (templates, CSS, migrations) is `embed`-bundled.
## Trust model (v1)
Single-user, Tailscale-only. No authentication layer. The deployment relies on:
- Dokploy app exposed only to Tailscale (no public DNS / reverse proxy outside Tailscale).
- msupabase reachable only inside the same Tailscale network.
- `PROJAX_DB_URL` is a Dokploy secret, not in the repo.
If projax later needs auth (multi-device, shared with people, etc.), the natural fit is the same Supabase auth used by flexsiebels — defer until projax has actually outgrown the Tailscale fence.
## Schema
```
projax.items (id, kind[], title, slug, path, parent_id, content_md,
aliases[], metadata jsonb, status, pinned, archived,
start_time, end_time, created_at, updated_at, deleted_at)
projax.item_links (item_id, ref_type, ref_id, rel, note, metadata, created_at)
projax.items_unified VIEW = projax.items UNION ALL adapter over mai.projects
```
A BEFORE trigger maintains `items.path` via parent walk and enforces structural rules (areas at root, projects not at root, no cycles). An AFTER trigger rewrites descendant paths on rename / re-parent.
A mai.projects row drops out of `items_unified` as soon as any `projax.item_links` row with `ref_type='mai-project'` points back at it — that's how the Promote flow makes the duplicate disappear without ever mutating `mai.projects`.
Migrations live in `db/migrations/`, are embedded into the binary, and applied lexicographically on boot.

35
deploy/dokploy.yaml Normal file
View File

@@ -0,0 +1,35 @@
# Dokploy app: projax
#
# Apply via Dokploy UI on mlake, or as a reference for the manual setup.
# Tailscale-only; no public exposure. Single replica, single tenant (m).
#
# Environment expected (set via Dokploy secrets, NEVER commit):
# PROJAX_DB_URL postgres://postgres:<pw>@<msupabase-tailscale-ip>:6789/postgres?sslmode=disable
# PROJAX_LISTEN_ADDR :8080 (default; Dokploy maps to public port)
# PROJAX_AUTO_MIGRATE on (default; set "off" to bypass embedded migrations on boot)
name: projax
service: projax
image:
build:
context: .
dockerfile: Dockerfile
domain:
host: projax.msbls.de
port: 8080
https: true
healthcheck:
path: /healthz
interval: 30s
timeout: 3s
retries: 3
resources:
cpu: 250m
memory: 128Mi
replicas: 1
restart: unless-stopped
env:
- PROJAX_LISTEN_ADDR=:8080
- PROJAX_AUTO_MIGRATE=on
secrets:
- PROJAX_DB_URL