Files
hops/README.md
T
tanshu a7c9324170 Model the build on mozimo and target emily
- Makefile: PLATFORMS, tags :<commit> and :main, build-check with --pull.
- deploy.sh: push, guards, git pull --ff-only, optional --prune, image_tag.
- Replace .containerignore with .dockerignore.
- Add the opt-in image prune to the hops role.
- Tag the Caddyfile task so --skip-tags caddy works before DNS cutover.
- Trim comments and rewrite build and deploy text in plain prose.
2026-10-20 17:47:49 +05:30

176 lines
7.8 KiB
Markdown

# Hops n Grains Microbrewery
A modern, full-stack craft microbrewery web application built with **TanStack Start** for the frontend and **PocketBase** for the backend, faithfully implementing the Google Stitch **Heritage & Harvest** tactile industrial design system.
---
## Architecture overview
```
hops/
├── backend/
│ ├── pocketbase # Linux AMD64 executable (WSL)
│ ├── pocketbase.exe # Windows AMD64 executable (PowerShell)
│ ├── pb_migrations/ # Database schema migrations & initial seeds
│ │ └── 1788363300_initial_schema_and_seed.js
│ └── pb_data/ # SQLite database & data storage
├── frontend/ # TanStack Start application
│ ├── src/
│ │ ├── routes/
│ │ │ ├── __root.tsx # Root layout, fonts, nav, footer, reservation context
│ │ │ ├── index.tsx # Home page (Google Stitch cinematic hero & craft overview)
│ │ │ ├── brews.tsx # Craft Brews page (Google Stitch menu with IBU/ABV filters)
│ │ │ ├── food.tsx # Global Comfort Food menu with beer pairing chips
│ │ │ ├── brewery.tsx # The Brewery & Heritage story and locations
│ │ │ └── live.tsx # Live On Tap draft board with keg telemetry
│ │ ├── components/
│ │ │ ├── Navbar.tsx # Frosted glass top bar with mobile menu drawer
│ │ │ ├── Footer.tsx # Dark industrial footer with newsletter signup
│ │ │ ├── BeerCard.tsx # Tactile craft beer card with IBU/ABV meters & pairings
│ │ │ ├── FoodCard.tsx # Food card with veg/non-veg tags & beer pairing
│ │ │ ├── ReservationModal.tsx # Interactive table reservation modal connected to PocketBase
│ │ │ └── TapHandle.tsx # Custom copper brewing handle slider from DESIGN.md
│ │ ├── lib/
│ │ │ └── pocketbase.ts # PocketBase SDK client with fallback data resilience
│ │ └── styles.css # Tailwind theme tokens, Playfair Display, Montserrat, textures
│ └── package.json
├── ansible/ # Deployment playbook (roles: register, hops, caddy)
├── Dockerfile # Multi-stage production container build
├── .dockerignore # Build-context excludes (podman and Docker)
├── .gitignore # Git repository ignore rules
├── docker-entrypoint.sh # Process supervisor running PB & Nitro
├── Makefile # Podman build targets (build-production / build-check)
├── deploy.sh # Build & push image, then run the Ansible deploy
├── package.json # Monorepo management scripts
└── README.md
```
---
## Running locally
### 1. Start the frontend and backend together
```bash
npm run dev
```
Or start them individually:
**Backend (PocketBase):**
```bash
# In WSL / Linux:
cd backend && ./pocketbase serve --http 127.0.0.1:8090
# In Windows PowerShell:
cd backend ; .\pocketbase.exe serve --http 127.0.0.1:8090
```
- API Endpoint: `http://127.0.0.1:8090`
- PocketBase Admin UI: `http://127.0.0.1:8090/_/`
- **Email:** `admin@hopsngrains.com`
- **Password:** `HopsPassword1234!`
**Frontend (TanStack Start):**
```bash
npm run dev:frontend
```
- Web Application: `http://localhost:3000`
---
## Deploying
`deploy.sh` pushes the current branch, refuses a dirty tracked tree, and
requires `HEAD == origin/main`. It builds that commit with podman and runs the
Ansible play on emily. Log in to the registry once per machine:
`podman login registry.tanshu.com`.
```bash
./deploy.sh # deploy HEAD
./deploy.sh --prune # also drop old images on emily, keep 3
./deploy.sh --force # skip the guards
```
The image is tagged with its commit id and `:main`. Rollback runs the same play
with an older `image_tag`:
```bash
make build-production # podman build HEAD, push :<commit> and :main
make build-check # build from HEAD, push nothing
cd ansible && ansible-playbook playbook.yml -e "image_tag=$(git rev-parse --short HEAD)"
```
The image builds for `linux/arm64` by default because emily is aarch64. Podman
has no built-in QEMU, so cross-building from an x86_64 controller needs binfmt
there (`qemu-user-static`). Override with `PLATFORMS=linux/amd64`:
```bash
make build-production PLATFORMS=linux/amd64
```
To run the production image locally:
```bash
podman build -t localhost/hops:dev .
podman run --rm -p 3000:3000 -p 8090:8090 \
-e PB_ADMIN_EMAIL=admin@hopsngrains.com -e PB_ADMIN_PASSWORD=HopsPassword1234! \
-v hops_dev_data:/app/pb_data localhost/hops:dev
```
### Ansible deployment
The controller needs `ansible`. The target host needs the shared Caddy rootless
Quadlet setup from the `ansible-playbooks` repo. That playbook owns the Caddy
unit, the rootless prerequisites, and the Caddyfile this play appends to.
The play targets `emily`, connects as the unprivileged container user, and does
not use `become`. The Quadlet units belong to that user, and the SSH session's
PAM context makes `systemctl --user` work.
```bash
cd ansible
ansible-playbook playbook.yml -e "image_tag=<commit>"
```
Skip the Caddyfile update until DNS points at emily. Otherwise Caddy retries
certificate issuance for a name that still resolves to monoco:
```bash
ansible-playbook playbook.yml -e "image_tag=<commit>" --skip-tags caddy
```
Deployment variables live in `ansible/vars/default.yml`. The play:
1. Writes `~/.config/containers/systemd/hops.network` with `NetworkName=hops_net`,
plus a drop-in that attaches the shared Caddy unit to it. Caddy can then
resolve `hops` by container name.
2. Logs in to the registry, pulls `registry.tanshu.com/hops:{image_tag}`, writes
`~/podman-data/hops/.env`, installs `hops.container`, starts it as a systemd
user service, and waits for the healthcheck.
3. Appends the site blocks to `~/podman-data/caddy/conf/Caddyfile` and reloads
Caddy. `www.hopsngrains.com` and the apex serve the frontend on port 3000 and
proxy `/api/*` and `/_/*` to PocketBase. `admin.hopsngrains.com` redirects to
the PocketBase admin UI.
Nothing is published on the host. Caddy reaches the container over `hops_net`.
The container runs as uid 0, so the unit maps it onto the podman user with
`UserNS=keep-id:uid=0,gid=0`. Files under `~/podman-data/hops` stay owned by
`tanshu`, and PocketBase's files need no `podman unshare` to read or back up.
DNS for all three hosts must point at emily before Caddy can issue certificates.
The first deploy starts with empty data. PocketBase applies the schema migration
in `backend/pb_migrations/` on first boot and recreates the seed beers and food.
---
## Features and pages
| Route | Name | Highlights |
|---|---|---|
| `/` | **Home** | Cinematic hero matching Google Stitch mockup, brewery pillars, signature craft beer preview, call-to-action buttons. |
| `/brews` | **Our Brews** | Interactive craft beer catalog matching Stitch design, style filters (Hefeweizen, Lager, IPA, Dark Ale, Cider), and custom copper **Tap Handle** IBU slider. |
| `/food` | **Global Comfort Food** | Handcrafted dining menu (Wood-fired pizzas, smash burgers, truffle fries) categorized with recommended craft beer pairings and veg toggles. |
| `/brewery` | **The Brewery** | 4-step industrial brewing science walkthrough (Mashing, Boiling, Fermentation, Cellar Pouring), and taproom details for Chandigarh & Panchkula. |
| `/live` | **Live On Tap** | Real-time draft board tracking active tap numbers, ABV, IBU, and live remaining keg volume percentages. |
| **Modal** | **Table Reservation** | Interactive booking form for Sector 9 Chandigarh or Sector 5 Panchkula, submitting live to PocketBase. |