- 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.
176 lines
7.8 KiB
Markdown
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. |
|