11 Commits
Author SHA1 Message Date
tanshu 82e5b7d0f6 Fix the Quadlet healthcheck so the deploy gate can pass
podman builds an OCI image by default and drops the Dockerfile's HEALTHCHECK
instruction, so the first deploy failed at "Wait for the container healthcheck
to pass" with "container ... has no defined healthcheck" even though the app was
up and healthy.

- Declare the same check on the greatbear.container unit (HealthCmd plus
  interval, timeout, retries, start period), the convention hops and menu-admin
  already use.
- Flush handlers right after starting, before the gate: the unit template's
  restart handler otherwise runs at the end of the play, so a run that changes
  only the unit would test the stale container.

Mirrors hops 7366596, so the two deployments keep the same shape.
2026-10-16 10:54:12 +05:30
tanshu 1c78fc5103 Deploy: push before building, and tag images with the commit
deploy.sh no longer needs a manual push before it, and no made-up tag:

- It pushes the current branch, refuses a dirty tracked tree, and requires
  HEAD == origin/main before anything is built, so the image can only be
  built for a commit the remote actually has. --force and --no-push skip the
  guards.
- The image is tagged :<commit> as well as :latest, and ansible takes
  -e image_tag=<commit>, so a deploy pins the exact revision; rollback is the
  same play with an older commit.
- Makefile builds `git archive HEAD` instead of cloning the remote (the push
  guard makes the local commit the remote state) and defaults PLATFORM to
  linux/arm64, because emily is aarch64 — the same default mozimo uses. Drop
  build-check-local; build-check already builds the committed tree.
2026-10-16 10:14:02 +05:30
tanshu 9620847108 Run the build on Podman and deploy as a rootless Podman Quadlet
- Makefile: podman build + push instead of docker buildx. build-production
  clones the git remote and builds from it; build-check / build-check-local
  build without pushing. Single-architecture (PLATFORM-overridable) rather
  than a multi-arch manifest.
- deploy.sh: connect as the unprivileged container user; no become.
- ansible: target emily, rootless Podman Quadlet behind the fleet's shared
  Caddy unit (caddy/playbook.yml in ansible-playbooks).
  - register role writes greatbear.network (NetworkName=greatbear_net) and
    the caddy.container.d drop-in that puts Caddy on it.
  - greatbear role: podman login/pull, ~/podman-data/greatbear + .env,
    greatbear.container (UserNS=keep-id:uid=0,gid=0, EnvironmentFile,
    Network, Volume, no published ports), systemctl --user start and a
    podman healthcheck gate, with a diagnostics rescue block.
  - caddy role appends the site blocks to the shared Caddyfile and reloads;
    handlers reload vs. recreate Caddy (a new network drop-in re-creates it).
  - drop the network role, the community.docker requirements and the
    ansible/files/ templates.
- .containerignore replaces .dockerignore (Buildah reads it natively).
- README: Podman build, rootless Quadlet deploy. No data migration step —
  there is no PocketBase state to carry over.
2026-10-16 10:09:12 +05:30
tanshu d9ba227657 Ingredients as many-to-many relations + VS Code/Antigravity launch.json
- new ingredients collection (uid, name, emoji icon, diet_class) synced
  by update-menu.mjs; menu_items.ingredients is an M2M relation — the
  script maps data-file uids to fresh PB record ids
- diet select removed: the green/red FSSAI badge is derived (non-veg when
  any linked ingredient is non-veg); 164 items tagged (11 ingredients)
- menu cards render ingredient chips (icon + name) under the description
- migration 1788500200_menu_ingredients.js (idempotent create — app.save
  returns nil on success in the JSVM, so don't test its return value)
- .vscode/launch.json: PocketBase + Vite dev, build+preview, menu sync,
  full-stack compound (dev PB on :8091 — 8090 is taken on this host)
2026-09-03 19:08:46 +05:30
tanshu e3444dbc60 Support decimal rupee prices (paise after the point)
PB number fields are float64 with onlyInt unset, so storage already
accepted decimals — verified 449.5 round-trips via API with no migration.
Cards now format fractional prices as ₹449.50 (whole rupees stay ₹449);
docs and type comments updated.
2026-09-03 18:42:07 +05:30
tanshu bea6710d38 Menu schema v3: variants as separate products, POS-ready fields
- variants split into their own menu_items whenever price differs
  (name + variant fields; 164 products, 24 variant groups)
- price back to number (whole rupees); no more '449/549' strings
- diet select (veg/nonveg/mixed) instead of nullable bool — PocketBase
  serializes empty bools as false, which misfiled mixed dishes
- new fields for POS: sku (unique GB-CAT-NNN), available (86 toggle),
  tax_category (default food-5-gst)
- migration 1788500100_menu_items_v3 replaces menu_items; resync via
  scripts/update-menu.mjs
- cards render 'Dish – Variant', badges from diet, hide unavailable
2026-09-03 18:39:00 +05:30
tanshu bcb41353ce Load the real food menu (11 categories, 137 items)
- app/src/data/menu-data.mjs: single source for PB sync + offline fallback
- pb/migrations/1788500000_menu_items_v2.js: price number -> text (supports
  ranges like 449/549), adds group field for veg/non-veg badges
- scripts/update-menu.mjs: replaces menu_categories/menu_items in any
  PocketBase (local dev or https://admin.greatbear.in)
- menu cards: category icons when no image, FSSAI-style dietary badge,
  text prices, optional pairing/tag footer
- content.json drops placeholder menu arrays (menu now lives in code)
2026-09-03 18:04:46 +05:30
tanshu 0ff90cde27 Rebuild deploy process on the project template
- Dockerfile: deps → builder → pb-downloader → runner stages, cross-arch
  (BUILDPLATFORM node stages, TARGETARCH PocketBase binary), Debian slim
  runner, VOLUME /app/pb_data, dual healthcheck, docker-entrypoint.sh
- docker-entrypoint.sh: bash supervisor replacing scripts/run-all.mjs
- Makefile: build-production (multi-arch push from git remote),
  build-check, build-check-local
- deploy.sh [tag]: make build-production then ansible-playbook with tag
- drop .env/.env.example (credentials live in ansible/vars/default.yml)
2026-09-03 10:24:48 +05:30
tanshu 757ec8aee2 Rebuild ansible on the project deploy template
- playbook.yml (hosts: monoco) + vars/default.yml, requirements.yml
- roles: network (shared docker network), greatbear (registry pull,
  bind mount /var/lib/greatbear/pb_data, .env upload, health wait),
  caddy (blockinfile snippet into the shared Caddyfile + docker exec reload)
- image honors PB_DATA_DIR and runs as root to match the bind-mount pattern
- frontend on www.greatbear.in (+apex) with /api/* and /_* proxied to
  PocketBase; admin.greatbear.in redirects to the admin UI
2026-09-03 10:14:11 +05:30
tanshu c03b9c582b Fix seed script paths outside the image; document full local dev flow 2026-09-03 09:57:30 +05:30
tanshu 7b070fde81 Initial build: Great Bear microbrewery site
- TanStack Start (React 19, Tailwind 4) frontend recreated from Stitch design
- PocketBase backend with schema migration + idempotent content seed
- Single Docker image running both services (non-root, healthchecked)
- deploy.sh to build & push to registry.tanshu.com
- Ansible playbook (roles: app, caddy) deploying to www.greatbear.in
  with Caddy TLS entries for the site and the PocketBase admin
2026-09-03 08:51:47 +05:30