Pansy is a self-hostable garden planner: a Go backend serving a JSON API plus an embedded React frontend, shipped as one static binary. This issue creates the backend skeleton everything else builds on: Go module, env config, gin HTTP server, SQLite store with an embedded migration runner, and the full initial schema.
Scope
go.mod: module gitea.stevedudenhoeffer.com/steve/pansy, Go 1.26.x. Pure-Go deps only — the build must work with CGO_ENABLED=0.
internal/config: load from env with defaults — PANSY_PORT (8080), PANSY_DB (./pansy.db), PANSY_BASE_URL, PANSY_REGISTRATION (open|closed, default open), PANSY_LOCAL_AUTH (default true), PANSY_OIDC_ISSUER/CLIENT_ID/CLIENT_SECRET/BUTTON_LABEL, trusted proxies. Auth values are plumbed now, consumed by #4/#5.
internal/store/sqlite.go: open modernc.org/sqlite with WAL + busy_timeout=5000 pragmas.
internal/store/migrate.go: tiny migration runner — numbered .sql files via embed.FS, applied in order, tracked in schema_migrations(version), run at startup, idempotent.
internal/store/migrations/0001_init.sql: full initial schema per DESIGN.md § Domain model — users, sessions, gardens, garden_shares, garden_objects, plants, plantings. All measurements in cm; version INTEGER NOT NULL DEFAULT 1 on user-mutable tables; unique (oidc_issuer, oidc_subject) on users; unique (garden_id, user_id) on garden_shares; indexes on foreign keys. Reserve shape='polygon' + points JSON column on garden_objects even though v1 only uses rect/circle.
CGO_ENABLED=0 go build ./... succeeds; go vet ./... is clean.
Running the binary creates and migrates the DB; curl localhost:8080/api/v1/healthz returns ok.
A second run is a no-op for migrations (idempotent).
Inspecting the DB shows all seven tables with the DESIGN.md columns.
Part of epic #20 · Design: [DESIGN.md](https://gitea.stevedudenhoeffer.com/steve/pansy/src/branch/main/DESIGN.md)
## Context
Pansy is a self-hostable garden planner: a Go backend serving a JSON API plus an embedded React frontend, shipped as one static binary. This issue creates the backend skeleton everything else builds on: Go module, env config, gin HTTP server, SQLite store with an embedded migration runner, and the full initial schema.
## Scope
- [ ] `go.mod`: module `gitea.stevedudenhoeffer.com/steve/pansy`, Go 1.26.x. Pure-Go deps only — the build must work with `CGO_ENABLED=0`.
- [ ] `internal/config`: load from env with defaults — `PANSY_PORT` (8080), `PANSY_DB` (`./pansy.db`), `PANSY_BASE_URL`, `PANSY_REGISTRATION` (`open`|`closed`, default open), `PANSY_LOCAL_AUTH` (default true), `PANSY_OIDC_ISSUER`/`CLIENT_ID`/`CLIENT_SECRET`/`BUTTON_LABEL`, trusted proxies. Auth values are plumbed now, consumed by #4/#5.
- [ ] `internal/store/sqlite.go`: open `modernc.org/sqlite` with WAL + `busy_timeout=5000` pragmas.
- [ ] `internal/store/migrate.go`: tiny migration runner — numbered `.sql` files via `embed.FS`, applied in order, tracked in `schema_migrations(version)`, run at startup, idempotent.
- [ ] `internal/store/migrations/0001_init.sql`: full initial schema per DESIGN.md § Domain model — `users`, `sessions`, `gardens`, `garden_shares`, `garden_objects`, `plants`, `plantings`. All measurements in cm; `version INTEGER NOT NULL DEFAULT 1` on user-mutable tables; unique `(oidc_issuer, oidc_subject)` on users; unique `(garden_id, user_id)` on garden_shares; indexes on foreign keys. Reserve `shape='polygon'` + `points` JSON column on garden_objects even though v1 only uses rect/circle.
- [ ] `internal/domain`: plain structs mirroring the schema + sentinel errors `ErrNotFound`, `ErrForbidden`, `ErrVersionConflict`.
- [ ] `internal/api`: gin server — `gin.New()` + slog-gin + `gin.Recovery()`; route `GET /api/v1/healthz` returning `{"ok":true}`.
- [ ] `cmd/pansy/main.go`: config → `store.Open` → migrate → api → `ListenAndServe` with graceful shutdown on SIGINT/SIGTERM.
## Out of scope
- Any endpoints beyond healthz (auth is #4/#5, gardens #7).
- Frontend (#2) and embedding/Makefile (#3).
- Entity query methods — each feature issue adds its own store queries.
## Key files & patterns
- gin + slog setup pattern: `/Users/steve/Projects/mort/pkg/ginserver/ginserver.go` (sibling repo on this machine).
- sqlite open/pragma pattern: `/Users/steve/Projects/executus/contrib/store/sqlite.go`.
- Schema source of truth: `DESIGN.md` § Domain model.
## Dependencies
None — first issue.
## Acceptance criteria
- `CGO_ENABLED=0 go build ./...` succeeds; `go vet ./...` is clean.
- Running the binary creates and migrates the DB; `curl localhost:8080/api/v1/healthz` returns ok.
- A second run is a no-op for migrations (idempotent).
- Inspecting the DB shows all seven tables with the DESIGN.md columns.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Part of epic #20 · Design: DESIGN.md
Context
Pansy is a self-hostable garden planner: a Go backend serving a JSON API plus an embedded React frontend, shipped as one static binary. This issue creates the backend skeleton everything else builds on: Go module, env config, gin HTTP server, SQLite store with an embedded migration runner, and the full initial schema.
Scope
go.mod: modulegitea.stevedudenhoeffer.com/steve/pansy, Go 1.26.x. Pure-Go deps only — the build must work withCGO_ENABLED=0.internal/config: load from env with defaults —PANSY_PORT(8080),PANSY_DB(./pansy.db),PANSY_BASE_URL,PANSY_REGISTRATION(open|closed, default open),PANSY_LOCAL_AUTH(default true),PANSY_OIDC_ISSUER/CLIENT_ID/CLIENT_SECRET/BUTTON_LABEL, trusted proxies. Auth values are plumbed now, consumed by #4/#5.internal/store/sqlite.go: openmodernc.org/sqlitewith WAL +busy_timeout=5000pragmas.internal/store/migrate.go: tiny migration runner — numbered.sqlfiles viaembed.FS, applied in order, tracked inschema_migrations(version), run at startup, idempotent.internal/store/migrations/0001_init.sql: full initial schema per DESIGN.md § Domain model —users,sessions,gardens,garden_shares,garden_objects,plants,plantings. All measurements in cm;version INTEGER NOT NULL DEFAULT 1on user-mutable tables; unique(oidc_issuer, oidc_subject)on users; unique(garden_id, user_id)on garden_shares; indexes on foreign keys. Reserveshape='polygon'+pointsJSON column on garden_objects even though v1 only uses rect/circle.internal/domain: plain structs mirroring the schema + sentinel errorsErrNotFound,ErrForbidden,ErrVersionConflict.internal/api: gin server —gin.New()+ slog-gin +gin.Recovery(); routeGET /api/v1/healthzreturning{"ok":true}.cmd/pansy/main.go: config →store.Open→ migrate → api →ListenAndServewith graceful shutdown on SIGINT/SIGTERM.Out of scope
Key files & patterns
/Users/steve/Projects/mort/pkg/ginserver/ginserver.go(sibling repo on this machine)./Users/steve/Projects/executus/contrib/store/sqlite.go.DESIGN.md§ Domain model.Dependencies
None — first issue.
Acceptance criteria
CGO_ENABLED=0 go build ./...succeeds;go vet ./...is clean.curl localhost:8080/api/v1/healthzreturns ok.