From 9466759aeb6c11857d88a91f6499cb458b58b57c Mon Sep 17 00:00:00 2001 From: mAi Date: Fri, 15 May 2026 13:26:53 +0200 Subject: [PATCH] build: Dockerfile + Dokploy manifest + README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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. --- .dockerignore | 9 +++++ .gitignore | 4 ++ Dockerfile | 15 ++++++++ README.md | 93 +++++++++++++++++++++++++++++++-------------- deploy/dokploy.yaml | 35 +++++++++++++++++ 5 files changed, 128 insertions(+), 28 deletions(-) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 deploy/dokploy.yaml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b9bb8eb --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +.git +.worktrees +.m +.claude +.mcp.json +*.md +!README.md +docs/ +**/*_test.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9887933 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/projax +/dist/ +*.log +.DS_Store diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a65177e --- /dev/null +++ b/Dockerfile @@ -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"] diff --git a/README.md b/README.md index 23bb0d1..570fcc1 100644 --- a/README.md +++ b/README.md @@ -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:@: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. diff --git a/deploy/dokploy.yaml b/deploy/dokploy.yaml new file mode 100644 index 0000000..bcf8c58 --- /dev/null +++ b/deploy/dokploy.yaml @@ -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:@: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