185 lines
6.5 KiB
Markdown
185 lines
6.5 KiB
Markdown
# MyKindlebox
|
||
|
||
A self-hosted rabbitry (rabbit breeding herd) management web app — track your
|
||
rabbits, their pedigrees, litters, and genetics. Self-hosted, with simple user
|
||
accounts sharing one herd, and designed to replace a legacy PHP
|
||
herd-management site.
|
||
|
||
The core differentiator: **adding a rabbit starts from a pedigree tree, not a
|
||
blank form.** The 3-generation ancestor tree (sire/dam and their sires/dams) is
|
||
the primary data-entry surface, with inline autocomplete against your existing
|
||
herd and graceful fallback to freeform text for bought-in or unknown ancestors.
|
||
|
||
## Features
|
||
|
||
- **Pedigree-first rabbit entry** — build the ancestor tree first, then fill in
|
||
details; unknown ancestors can be stored as plain text so entry is never
|
||
blocked by missing records.
|
||
- **Herd management** — name, sex, DOB, weight, and status
|
||
(`active` / `for_sale` / `on_hold` / `sold` / `deceased` / `other`).
|
||
- **Litters** — record kindle dates and kit counts; the kit list is derived
|
||
live from Rabbit records (no separate Kit table).
|
||
- **Genetics** — per-rabbit loci (`A/B/C/D/E/En/V`) and a session-only pairing
|
||
calculator for predicting offspring.
|
||
- **Dashboard** — herd counts derived live from rabbit status.
|
||
- **Pedigree printing**, CSV import/export, and a configurable weight display
|
||
(decimal lbs or lb + oz).
|
||
- **PWA-ready** with a mobile bottom nav, installable on phones/tablets.
|
||
|
||
## Tech stack
|
||
|
||
| Layer | Choice |
|
||
| -------- | ----------------------------------------------------- |
|
||
| Frontend | React 19 + Vite (PWA-ready) |
|
||
| Routing | React Router v7 |
|
||
| State | React Query (server sync) |
|
||
| Styling | Tailwind CSS |
|
||
| Backend | Node.js + Express 5 (REST API) |
|
||
| Database | SQLite via Drizzle ORM (single-file DB) |
|
||
| Auth | User accounts, shared herd (scrypt + DB sessions) |
|
||
| Hosting | Docker Compose (Nginx + Node + SQLite volume) |
|
||
|
||
## Project layout
|
||
|
||
```
|
||
mykindlebox/
|
||
├── client/ React + Vite frontend
|
||
├── server/ Express + SQLite/Drizzle API
|
||
└── docker-compose.yml Production stack (nginx + node + volume)
|
||
```
|
||
|
||
## Development
|
||
|
||
Run the backend and frontend in two terminals.
|
||
|
||
**Backend** (`server/`):
|
||
|
||
```bash
|
||
cd server
|
||
npm install
|
||
npm run dev # API on http://localhost:3001 (tsx watch, hot reload)
|
||
```
|
||
|
||
Other scripts: `npm run build` (typecheck + compile to `dist/`),
|
||
`npm start` (run compiled server), `npm test`.
|
||
|
||
**Frontend** (`client/`):
|
||
|
||
```bash
|
||
cd client
|
||
npm install
|
||
npm run dev # Vite dev server on http://localhost:5173
|
||
```
|
||
|
||
The dev server proxies `/api` and `/uploads` to `localhost:3001`. Other
|
||
scripts: `npm run build`, `npm run lint`, `npm test`.
|
||
|
||
> The client talks to the API over **relative URLs** (`/api`, `/uploads`) so the
|
||
> app works over LAN and as an installed PWA — never hardcode a host.
|
||
|
||
## Deployment (Docker Compose)
|
||
|
||
From the `mykindlebox/` directory:
|
||
|
||
```bash
|
||
docker compose up --build
|
||
```
|
||
|
||
The app is served on **http://localhost:8080** — nginx serves the built client
|
||
and reverse-proxies `/api` + `/uploads` to the Node server. The SQLite database
|
||
and uploaded photos live in the `server-data` Docker volume.
|
||
|
||
To serve on a different host port, set `MYKINDLEBOX_PORT` (e.g. in a `.env` file
|
||
next to `docker-compose.yml`):
|
||
|
||
```env
|
||
MYKINDLEBOX_PORT=3000
|
||
```
|
||
|
||
Then re-run `docker compose up -d` — the app is served on that port instead of
|
||
8080. (Only the host side changes; inside the compose network nginx still
|
||
listens on 80.)
|
||
|
||
### Updating an existing deployment
|
||
|
||
Compose builds from whatever is checked out on the host, so pull first, then
|
||
rebuild. Run from the repo root on the server:
|
||
|
||
```bash
|
||
# 1. Back up the database (schema migrations run automatically on startup)
|
||
cd mykindlebox
|
||
docker compose cp server:/app/data ./data-backup-$(date +%F)
|
||
|
||
# 2. Pull the latest code
|
||
cd ..
|
||
git checkout main
|
||
git pull
|
||
git log --oneline -1 # confirm you're on the commit you expect
|
||
|
||
# 3. Rebuild and restart both services
|
||
cd mykindlebox
|
||
docker compose up --build -d
|
||
```
|
||
|
||
**Verify both containers were recreated:**
|
||
|
||
```bash
|
||
docker compose ps
|
||
```
|
||
|
||
Both `client` and `server` should show a recent **CREATED** time (seconds or
|
||
minutes ago), and `server` should reach **(healthy)** within ~15 seconds. If
|
||
either still shows an old CREATED time, Compose reused a cached image — force
|
||
it:
|
||
|
||
```bash
|
||
docker compose build --no-cache server # or client
|
||
docker compose up -d --force-recreate server
|
||
```
|
||
|
||
The client and server must be from the same version; a new client against an
|
||
old server will fail on login and newer pages. If the server never becomes
|
||
healthy, check `docker compose logs server --tail 50`.
|
||
|
||
Finally, hard-refresh the browser (Ctrl+Shift+R). On an installed phone PWA,
|
||
fully close and reopen the app to pick up the new build. Once everything looks
|
||
right, the `data-backup-*` folder can be deleted.
|
||
|
||
### Pedigree scanning (optional)
|
||
|
||
**Add Rabbit → Scan a pedigree** reads a photo of a paper pedigree (any layout,
|
||
printed or handwritten) and fills in the tree. It uses the Claude API, so it
|
||
needs an API key from [console.anthropic.com](https://console.anthropic.com) and
|
||
internet access on the server. Add it to `.env` next to `docker-compose.yml`:
|
||
|
||
```env
|
||
ANTHROPIC_API_KEY=sk-ant-...
|
||
```
|
||
|
||
Then run `docker compose up --build -d`. Without a key the scan button is hidden
|
||
and everything else works as before. Each scan costs roughly 5–10¢ in API usage.
|
||
The photo is sent to Anthropic for the scan and is not stored by MyKindlebox.
|
||
|
||
### Authentication
|
||
|
||
The app uses **user accounts** — everyone shares the same herd; accounts are
|
||
separate logins with their own theme and weight-display preference. On first
|
||
launch the app shows a one-time **create your account** screen; add more people
|
||
later under **Settings → Users**. Passwords are hashed (scrypt) and sessions
|
||
last 30 days, surviving server restarts.
|
||
|
||
> **Upgrading from the password-only version?** The old `MYKINDLEBOX_PASSWORD`
|
||
> env var is no longer used — remove it from your `.env`. On your next visit
|
||
> you'll see the one-time account-creation screen; your herd data is untouched.
|
||
|
||
## Data & backups
|
||
|
||
The herd database (`server/data/`), uploaded photos, and `.env` files are
|
||
**gitignored** — they are never committed. In production they persist in the
|
||
`server-data` volume; back that volume up to preserve your data. A quick
|
||
snapshot from a running stack:
|
||
|
||
```bash
|
||
docker compose cp server:/app/data ./data-backup-$(date +%F)
|
||
```
|