commit 42b1060c1948ce972da5a90654aed6c427afc88a Author: root Date: Mon Sep 21 17:27:38 2026 +0200 Initial setup: OpenResto deployment for Lunkaberg breakfast booking Adapts upstream's release docker-compose.yml to run behind this server's Nginx Proxy Manager on the shared my-main-net network instead of publishing host ports. Includes docs/ for session continuity. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_014vNjtg18oTQcv8dtjwnT69 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..40ce273 --- /dev/null +++ b/.env.example @@ -0,0 +1,35 @@ +# Copy to .env and fill in real values. Never commit the real .env. + +# --- Required --- +# JWT signing key, min 32 chars: openssl rand -base64 48 +JWT_KEY= + +# Public URL this deployment is served on +CORS_ORIGINS=https://frukost.lunkaberg.se + +# Seeds the admin account on first boot only +ADMIN_EMAIL= +ADMIN_PASSWORD= + +# --- Optional: SMTP for booking confirmation emails --- +SMTP_HOST= +SMTP_PORT=587 +SMTP_USERNAME= +SMTP_PASSWORD= +SMTP_FROM_EMAIL= +SMTP_FROM_NAME=Lunkaberg Frukost +SMTP_ENABLE_SSL=true + +# --- Optional: push notifications --- +VAPID_PUBLIC_KEY= +VAPID_PRIVATE_KEY= +VAPID_SUBJECT= + +# --- Optional: Apple/Google Wallet passes --- +APPLE_PASS_TYPE_ID= +APPLE_TEAM_ID= +APPLE_PASS_CERTIFICATE_PATH= +APPLE_PASS_CERTIFICATE_PASSWORD= +APPLE_WWDR_CERTIFICATE_PATH= +GOOGLE_WALLET_ISSUER_ID= +GOOGLE_WALLET_SERVICE_ACCOUNT_KEY_PATH= diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..06f8286 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.env +wallet/ +well-known/ diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..29d511f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,100 @@ +# OpenResto — self-hosted breakfast booking (Lunkaberg) +# +# Adapted from the official release compose (v2.2.0) to run behind the +# existing Nginx Proxy Manager setup on this server: no host ports are +# published, all services join the shared `my-main-net` network, and NPM +# proxies straight to the `lunkaberg-reverse-proxy` container on port 80. +# +# Backup: see https://github.com/karanshukla/openresto/blob/main/docs/backup-restore.md + +volumes: + db_data: + media_data: + +networks: + main: + external: true + name: my-main-net + +services: + backend: + image: ghcr.io/karanshukla/openresto-backend:2.2.0 + container_name: lunkaberg-backend + restart: unless-stopped + networks: + - main + environment: + - ASPNETCORE_ENVIRONMENT=Production + - PORT=8080 + - CORS_ORIGINS=${CORS_ORIGINS} + - JWT_KEY=${JWT_KEY} + - ADMIN_EMAIL=${ADMIN_EMAIL} + - ADMIN_PASSWORD=${ADMIN_PASSWORD} + - CONNECTION_STRING=Data Source=/data/openresto.db + - DATA_PROTECTION_KEYS_PATH=/data/dp-keys + - Vapid__PublicKey=${VAPID_PUBLIC_KEY:-} + - Vapid__PrivateKey=${VAPID_PRIVATE_KEY:-} + - Vapid__Subject=${VAPID_SUBJECT:-} + - GuestPush__ReminderLeadHours=${GUEST_PUSH_REMINDER_LEAD_HOURS:-24,2} + - GuestPush__ExpoAccessToken=${EXPO_ACCESS_TOKEN:-} + - Wallet__Apple__PassTypeIdentifier=${APPLE_PASS_TYPE_ID:-} + - Wallet__Apple__TeamIdentifier=${APPLE_TEAM_ID:-} + - Wallet__Apple__CertificatePath=${APPLE_PASS_CERTIFICATE_PATH:-} + - Wallet__Apple__CertificatePassword=${APPLE_PASS_CERTIFICATE_PASSWORD:-} + - Wallet__Apple__WwdrCertificatePath=${APPLE_WWDR_CERTIFICATE_PATH:-} + - Wallet__Google__IssuerId=${GOOGLE_WALLET_ISSUER_ID:-} + - Wallet__Google__ServiceAccountKeyPath=${GOOGLE_WALLET_SERVICE_ACCOUNT_KEY_PATH:-} + - EmailSettings__Host=${SMTP_HOST:-} + - EmailSettings__Port=${SMTP_PORT:-587} + - EmailSettings__Username=${SMTP_USERNAME:-} + - EmailSettings__Password=${SMTP_PASSWORD:-} + - EmailSettings__FromEmail=${SMTP_FROM_EMAIL:-} + - EmailSettings__FromName=${SMTP_FROM_NAME:-Lunkaberg Frukost} + - EmailSettings__EnableSsl=${SMTP_ENABLE_SSL:-true} + volumes: + - db_data:/data + - media_data:/app/wwwroot/media + - ./wallet:/wallet:ro + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/api/health"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 20s + + frontend: + image: ghcr.io/karanshukla/openresto-frontend:2.2.0 + container_name: lunkaberg-frontend + restart: unless-stopped + networks: + - main + environment: + - EXPO_PUBLIC_API_URL=/api + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8081"] + interval: 10s + timeout: 5s + retries: 3 + + reverse-proxy: + image: ghcr.io/karanshukla/openresto-nginx:2.2.0 + container_name: lunkaberg-reverse-proxy + restart: unless-stopped + networks: + - main + # No host port published — Nginx Proxy Manager reaches this container + # directly on my-main-net at lunkaberg-reverse-proxy:80. + environment: + - PORT=80 + - BACKEND_HOST=backend + - BACKEND_PORT=8080 + - FRONTEND_HOST=frontend + - FRONTEND_PORT=8081 + volumes: + - media_data:/usr/share/nginx/html/media:ro + - ./well-known:/usr/share/nginx/html/.well-known:ro + depends_on: + backend: + condition: service_healthy + frontend: + condition: service_healthy diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md new file mode 100644 index 0000000..0421ee2 --- /dev/null +++ b/docs/DECISIONS.md @@ -0,0 +1,28 @@ +# Decisions — Lunkaberg Frukost (OpenResto) + +## 2026-09-21 — Use OpenResto rather than build custom +Per wanted to evaluate get.openres.to for a client breakfast-booking site. +It's MIT-licensed, self-hosted, ASP.NET Core + React + SQLite, ships +pre-built multi-arch Docker images and is explicitly designed to sit behind +a reverse proxy like NPM. Decided to deploy it as-is rather than build a +custom PHP tool, since it already covers table holds, multi-location, +service periods, and an admin dashboard out of the box. + +## 2026-09-21 — Git via self-hosted Gitea, not the usual live-edit workflow +Per's standing default for hobby projects is no git / live-editing on the +server. This is client work, so he's using his Gitea instance +(gitea.axbom.com) instead — repo `root/LunkabergB`. `.env` (secrets) is +gitignored; `.env.example` documents the required variables. + +## 2026-09-21 — No host ports published for the OpenResto containers +Upstream's docker-compose.yml publishes the bundled nginx reverse-proxy +container on host port 80 by default, which would collide with this +server's Nginx Proxy Manager (already bound to 80/443). Adapted the compose +file to join the existing `my-main-net` Docker network instead (same +pattern as vox, remark42, etc.) — NPM proxies straight to +`lunkaberg-reverse-proxy:80` over the Docker network, no host port needed. + +## 2026-09-21 — Admin email defaulted to per@axbom.se +Used as a placeholder so there's a working login immediately; this should +be revisited once it's clear who administers bookings day-to-day for the +client (Per vs. someone at Lunkaberg). diff --git a/docs/STATE.md b/docs/STATE.md new file mode 100644 index 0000000..20e8d3e --- /dev/null +++ b/docs/STATE.md @@ -0,0 +1,39 @@ +# State — Lunkaberg Frukost (OpenResto) + +## What this is +Client project for lunkaberg.se — a breakfast booking service, built on +[OpenResto](https://get.openres.to) (open source, MIT), self-hosted at +`frukost.lunkaberg.se`. + +## Stack +- OpenResto v2.2.0, official pre-built images (`ghcr.io/karanshukla/openresto-*`) +- Backend: ASP.NET Core 10 + SQLite (in `db_data` volume) +- Frontend: React (Expo/React Native web build) +- Reverse proxy: bundled nginx container (`lunkaberg-reverse-proxy`), fronted + by this server's Nginx Proxy Manager — no host ports published, everything + rides the shared `my-main-net` Docker network like the other app projects + on this box. +- Location: `/var/www/lunkaberg/` +- Git: tracked, remote at `https://gitea.axbom.com/root/LunkabergB.git` + (this is a client project, so — unlike most of Per's hobby projects — + it's using real version control instead of live-editing.) + +## Status as of 2026-09-21 +- [x] Project directory created, git initialized, Gitea remote added +- [x] docker-compose.yml adapted from upstream release for the my-main-net / + NPM pattern (no host port conflicts with NPM's own 80/443) +- [x] .env created with generated JWT_KEY and admin password (not committed — + see .env.example for the template) +- [ ] `docker compose up -d` — pulling images / bringing the stack up +- [ ] DNS: A record for frukost.lunkaberg.se → 64.112.127.212 (Per manages + lunkaberg.se DNS himself) +- [ ] NPM proxy host for frukost.lunkaberg.se → lunkaberg-reverse-proxy:80 +- [ ] First login / admin dashboard walkthrough +- [ ] Decide: SMTP for booking confirmation emails (needs a mailbox at + lunkaberg.se, separate from Per's personal Migadu setup) +- [ ] Decide: push notifications (VAPID), Apple/Google Wallet passes — + left disabled for now, app degrades gracefully without them + +## Known issues / watch items +- Server disk was at 87% (6.5GB free) before pulling OpenResto images — + keep an eye on `df -h` after this and future installs. diff --git a/docs/TODO.md b/docs/TODO.md new file mode 100644 index 0000000..742a3e8 --- /dev/null +++ b/docs/TODO.md @@ -0,0 +1,24 @@ +# TODO — Lunkaberg Frukost (OpenResto) + +## Next up +1. Bring the stack up (`docker compose up -d`) and confirm all three + containers report healthy. +2. Per: add DNS A record for `frukost.lunkaberg.se` → `64.112.127.212`. +3. Create NPM proxy host: `frukost.lunkaberg.se` → `lunkaberg-reverse-proxy:80`, + request Let's Encrypt cert, force SSL. +4. Log into the admin dashboard with the generated credentials (see .env), + change the admin password from the generated one via the UI if it + offers that, and set up the restaurant's first location (timezone, + hours, tables, floor sections). +5. Push the initial commit to Gitea once Per confirms he's ready. + +## Open questions for Per +- Who is the actual admin user for the client (Per, or someone at + Lunkaberg)? Affects ADMIN_EMAIL and whether Per hands off credentials. +- Want booking confirmation emails? Needs SMTP creds — likely a new mailbox + at lunkaberg.se via Migadu, separate from Per's personal one. +- Any interest in push notifications or Apple/Google Wallet passes, or + leave those disabled (current default)? +- Branding: restaurant name / primary color / icon are configurable live + from the admin dashboard, no redeploy needed — revisit once client has + a look.