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
This commit is contained in:
2026-09-03 08:51:47 +05:30
commit 7b070fde81
66 changed files with 8375 additions and 0 deletions
+118
View File
@@ -0,0 +1,118 @@
# The Great Bear — Microbrewery Website
Full-stack site for The Great Bear microbrewery (SCO 32, Sector 26, Chandigarh), built from the
Stitch "Industrial Hospitality" design.
| Layer | Tech |
| --------- | ----------------------------------------------------------- |
| Frontend | [TanStack Start](https://tanstack.com/start) (React 19, Vite 8, Tailwind CSS 4, SSR) |
| Backend | [PocketBase](https://pocketbase.io) v0.40 — CMS content, form intake, admin UI |
| Runtime | Single Docker image running both services |
| Proxy | Caddy (automatic HTTPS) on the target host |
| Deploys | `deploy.sh` (build + push) → Ansible playbook (pull + run) |
## Pages
- `/` — hero, stats, featured brews, experiences, testimonials, quick table booking
- `/our-brews` — flagship banner, core lineup, seasonal releases, tasting-flight builder
- `/menu` — filterable gastropub menu with beer pairings
- `/visit` — reservation form, contact info, map
All structured content (brews, menu, testimonials, stats, contact info) is served from
PocketBase and editable in the admin UI. If PocketBase is unreachable, the site falls back to
bundled seed content so it never renders empty.
## Repository layout
```
app/ TanStack Start application
src/routes/ Pages (/, /our-brews, /menu, /visit)
src/components/ Shared UI + client forms
src/lib/ Server functions & PocketBase client
src/data/content.json Single source of truth for seed + fallback content
public/images/ Design assets (downloaded from the Stitch export)
pb/migrations/ PocketBase schema (snapshot migration, auto-applied)
scripts/
run-all.mjs Container supervisor: PocketBase + site server
seed.mjs Idempotent content seeder (create-if-missing by uid)
create-collections.sh Dev helper used to build the schema migration
ansible/ Deployment playbook (roles: app, caddy)
Dockerfile Multi-stage image build
deploy.sh Build & push image to the private registry
```
## Local development
```bash
cd app
npm install
npm run dev # http://localhost:3000 (uses bundled seed data if PocketBase is down)
```
To run PocketBase locally (optional):
```bash
./.tmp-pb/pocketbase serve --http=127.0.0.1:8090 --dir=./pb_data --migrationsDir=./pb/migrations
```
`npm run build` produces `.output/server/index.mjs`, started with `npm start`.
## Deploying
### 1. Build and push the image
```bash
cp .env.example .env # then fill in registry credentials
./deploy.sh
```
Tags pushed: `registry.tanshu.com/tanshu/greatbear:latest` and `:<git-sha>`.
### 2. Deploy with Ansible
Requirements on the controller: `ansible` + `community.docker` collection
(`ansible-galaxy collection install community.docker`), SSH access to the host.
Requirements on the host (`www.greatbear.in`): Docker and Caddy already installed.
```bash
cd ansible
# edit group_vars/greatbear/vars.yml if needed (or encrypt it with ansible-vault)
ansible-playbook site.yml # add -i inventory/hosts.yml if not using ansible.cfg
```
The playbook:
1. Logs into the registry on the host, pulls the image, and (re)creates the `greatbear`
container with ports bound to loopback (`127.0.0.1:3000`, `127.0.0.1:8090`) and a named
volume `greatbear_pb_data:/data` for the database.
2. Waits for PocketBase + site health checks.
3. Writes `/etc/caddy/caddy.d/greatbear.conf` with both site blocks and reloads Caddy:
- `www.greatbear.in``127.0.0.1:3000` (the site)
- `admin.greatbear.in``127.0.0.1:8090` (PocketBase API + admin UI)
DNS records for both domains must point at the host; Caddy obtains TLS certificates
automatically on first request.
## PocketBase admin
- URL: `https://admin.greatbear.in/_/`
- First container start bootstraps the superuser from `PB_SUPERUSER_EMAIL` /
`PB_SUPERUSER_PASSWORD` (set in `ansible/group_vars/greatbear/vars.yml`, idempotent).
Content collections: `brews`, `menu_categories`, `menu_items`, `experiences`, `testimonials`,
`stats`, `pairings`, `settings`. Reservation/contact submissions land in `reservations`
(public create only — readable by admins).
The seed only creates records that are missing (matched by `uid`), so edits made in the admin
are never overwritten by redeploys. Schema changes belong in `pb/migrations/`.
## Container environment
| Variable | Default | Purpose |
| ----------------------- | ------------------------ | -------------------------------------- |
| `PORT` | `3000` | Site server port |
| `PB_URL` | `http://127.0.0.1:8090` | PocketBase URL used by the site server |
| `PB_SUPERUSER_EMAIL` | — | Initial admin (bootstrapped at start) |
| `PB_SUPERUSER_PASSWORD` | — | Initial admin password |