Commit Graph

10 Commits

Author SHA1 Message Date
JP
181ef13c7e Make the image bucket private
The bucket carried an anonymous download policy, which is what made the
old unauthenticated proxy work. Now that images are served through a
session-gated route using credentials, anonymous access is unnecessary
and was the second half of the public exposure.

Applied on the running host; both compose files updated so bringing the
stack up elsewhere does not re-apply 'download'.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 20:31:01 +00:00
JP
87f951bf94 Run natively under systemd; Docker was unworkable in this LXC
Docker in this unprivileged Proxmox LXC was broken two independent ways:
AppArmor could not load the docker-default profile, so the daemon could
not start any new container, and runc could not set the
net.ipv4.ip_unprivileged_port_start sysctl, which is why every service
needed network_mode: host. `docker build` was impossible outright since
Docker 29 removed the classic builder. The stack only survived because
the containers predated the breakage - a reboot would have left the app
down, and nothing could be redeployed.

Postgres, MinIO and the app now run natively under systemd. Deploys
build out-of-place into releases/<sha> and swap a symlink, so the build
happens while the old release keeps serving and downtime is the ~3s
restart rather than the ~3min build. Rollback is the same swap in
reverse with no rebuild.

Dockerfile and docker-compose.prod.yml are unchanged and still work on
a normal host; the compose app service gains `build: .` so it can come
up on a VPS that cannot reach the LAN-only Gitea registry.

Documents three traps found during the migration: rewrites() in
next.config.mjs is evaluated at build time so MINIO_ENDPOINT changes
silently do nothing, prisma migrate deploy would fail because
production has no _prisma_migrations table, and the image proxy relies
on the bucket's anonymous-download policy.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 17:58:34 +00:00
JP
a0e1619072 Route all AI features through the Switchboard gateway
Replace the direct Anthropic and OpenAI integrations with a single
provider that talks to Switchboard, an OpenAI-compatible gateway that
routes each request to the best available model. The app no longer pins
a model id anywhere: it sends switchboard/auto and lets the gateway
choose, then logs which model answered and what it cost.

Routing levers are set per feature in src/lib/ai/routing.ts. Three of
those choices came from measuring against the live gateway:

- category and prefer_free are set explicitly on every request. An API
  key carries its own routing defaults, and anything left unset inherits
  them - drink prompts were being sent to a free coding model.
- Token budgets are generous because the router may pick a reasoning
  model, and reasoning tokens come out of the same max_tokens budget as
  the answer. At 512 tokens a request returned null content; at 4096 the
  same request returned correct JSON.
- No tier lever on text features. tier "cheap" pinned a slow reasoning
  model (42-180s, two timeouts and one truncated response in five
  trials) and tier "frontier" escalated as far as Opus at $0.02 a call,
  while unconstrained routing answered in about a second. Vision keeps
  "frontier", where the accuracy is worth a few tenths of a cent.

Gateway failures are mapped to actionable messages rather than passed
through: a 401 relayed as 401 would read as an expired session and
bounce the user to login, and a 429 would collide with the app's own
rate limiter.

Also collapses the key lookup that was duplicated across ten call sites
into getUserProvider(), which fixes a latent bug where a bare findFirst
with no ordering let different features pick different providers.

Existing claude/openai key rows are ignored at runtime and offered for
removal in Settings, so no migration is needed before deploying.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 16:41:00 +00:00
JP Scott
7c41b15ecc Update prod compose to pull from Gitea container registry
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 23:01:47 -07:00
JP Scott
4ed53d0fd7 Switch to host networking for Proxmox LXC compatibility
network_mode: host avoids Docker creating separate network namespaces
which trigger sysctl writes blocked in LXC containers. All service
references updated from container names to localhost.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 15:37:14 -07:00
JP Scott
c401c8f2e0 Add privileged: true to all services for LXC Docker compatibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 15:12:57 -07:00
JP Scott
959cf57a46 Add apparmor:unconfined to all services for Proxmox LXC compatibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 14:45:35 -07:00
JP Scott
491f8f2c7b Switch to pre-built Docker Hub image for production
- Push app image to jpscott84/drinktracker on Docker Hub
- docker-compose.prod.yml uses image instead of build
- install.sh pulls image instead of building from source
- Much faster deploys (no npm ci/build on target server)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 14:38:44 -07:00
JP Scott
1d454d84b2 Add production install script and migrate service
- install.sh: Interactive setup script for Linux VPS/LXC deployment
  - Checks prerequisites (Docker, Docker Compose, OpenSSL)
  - Auto-generates all secrets (Postgres, MinIO, NextAuth, encryption)
  - Creates .env.production with proper Docker service hostnames
  - Builds and starts all services via docker-compose.prod.yml
  - Health check loop with status reporting
  - Idempotent (safe to re-run)

- docker-compose.prod.yml: Add migrate service
  - One-shot container that runs prisma db push before app starts
  - App depends on migrate completing successfully
  - Override DATABASE_URL and MINIO_ENDPOINT for Docker networking

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 13:40:48 -07:00
JP Scott
969bc9347a Initial commit: DrinkTracker full-stack app
Next.js 14 drink collection tracker with AI-powered search,
menu scanning, ratings, wishlist, sharing, and CSV backup/restore.

Features:
- Auth (credentials + OAuth ready)
- Drink collection with ratings and reviews
- AI search via Claude/OpenAI with search history
- Menu photo scanning with AI extraction
- Wishlist / Try Later system
- Public sharing via slug URLs
- CSV backup and restore (merge/replace modes)
- Docker Compose for Postgres + MinIO + dev server

Security: docker-compose files use env var interpolation
instead of hardcoded secrets.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 12:42:11 -07:00