← back to Designer Wallcoverings
appointments deploy artifacts: api. nginx vhost + certbot + FINAL keep-in-dev runbook (Option A; DNS A-record live in Cloudflare)
f5cca320d6825adce6d19c8089ac8ef3c6814d05 · 2026-06-29 10:27:43 -0700 · Steve Abrams
Files touched
A shopify/quote-api/appointments/deploy/FINAL-deploy-keep-in-dev.shA shopify/quote-api/appointments/deploy/api.designerwallcoverings.com.conf
Diff
commit f5cca320d6825adce6d19c8089ac8ef3c6814d05
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Jun 29 10:27:43 2026 -0700
appointments deploy artifacts: api. nginx vhost + certbot + FINAL keep-in-dev runbook (Option A; DNS A-record live in Cloudflare)
---
.../deploy/FINAL-deploy-keep-in-dev.sh | 83 ++++++++++++++++++++++
.../deploy/api.designerwallcoverings.com.conf | 76 ++++++++++++++++++++
2 files changed, 159 insertions(+)
diff --git a/shopify/quote-api/appointments/deploy/FINAL-deploy-keep-in-dev.sh b/shopify/quote-api/appointments/deploy/FINAL-deploy-keep-in-dev.sh
new file mode 100644
index 00000000..3fcca35d
--- /dev/null
+++ b/shopify/quote-api/appointments/deploy/FINAL-deploy-keep-in-dev.sh
@@ -0,0 +1,83 @@
+#!/usr/bin/env bash
+# ═══════════════════════════════════════════════════════════════════════════════
+# DW Appointments — FINAL DEPLOY (Option A · KEEP IN DEV) — run by hand, top→bottom
+#
+# Decided: edge routing = Option A, api.designerwallcoverings.com (Steve 2026-06-29).
+# KEEP IN DEV: stand up the backend + api host so it's fully testable, but DO NOT
+# create the live Shopify page and DO NOT publish theme 5.9.0. Verify via the dev
+# theme preview only. Steps 6–7 are intentionally HELD.
+#
+# Each block is a gated action. Recon (2026-06-29): koroseal-quote-api online :3001;
+# pg MISSING + no DATABASE_URL (step 1 fixes); appt_* not yet on canonical dw_unified
+# (step 2 creates); dw_admin role exists; GEORGE_* already in the app .env.
+# ═══════════════════════════════════════════════════════════════════════════════
+set -euo pipefail
+echo "Reference runbook — run blocks deliberately, not via ./this."; exit 0
+
+SRV="my-server" # ssh alias → root@45.61.58.125 (root over ssh)
+APP=/root/Projects/koroseal-quote-api
+LOCAL_APPT=~/Projects/Designer-Wallcoverings/shopify/quote-api/appointments
+
+# ─── DNS (Option A) — usually already done via domain-suite MCP; verify it resolves ──
+dig +short api.designerwallcoverings.com # expect 45.61.58.125
+
+# ─── 1. DEPLOY BACKEND into the live koroseal app ─────────────────────────────
+rsync -av --exclude 'run-local.js' --exclude 'render-preview.js' --exclude 'drive-preview.mjs' \
+ --exclude 'preview.html' "$LOCAL_APPT/" "$SRV:$APP/appointments/"
+ssh "$SRV" "cd $APP && npm install pg@^8 --omit=dev"
+ssh "$SRV" "cp $APP/server.js $APP/server.js.bak-pre-appointments-\$(date +%Y%m%d-%H%M%S)"
+# then edit $APP/server.js — add, right AFTER the /api/sku-inquiry line and BEFORE
+# the catch-all 404 handler:
+# app.use(require('./appointments/router'));
+
+# ─── 1b. App env — append to $APP/.env (secrets via /secrets, see step 4) ─────
+cat <<'ENV'
+DATABASE_URL=postgres://dw_admin@127.0.0.1:5432/dw_unified
+APPT_OWNER_EMAIL=info@designerwallcoverings.com
+APPT_PUBLIC_BASE=https://api.designerwallcoverings.com
+DW_SHOWROOM_LOCATION=Designer Wallcoverings Showroom · 15442 Ventura Blvd #102, Sherman Oaks, CA 91403
+GOOGLE_OAUTH_REDIRECT_URI=https://api.designerwallcoverings.com/api/appointments/oauth/callback
+# CORS_ORIGIN: set to the storefront primary domain so the existing koroseal-quote /
+# sku-inquiry POSTs are allowed cross-origin (the appointments router is already *).
+CORS_ORIGIN=https://www.designerwallcoverings.com
+ENV
+
+# ─── 2. CANONICAL MIGRATION on dw_unified (postgres role — required over ssh) ──
+scp "$LOCAL_APPT/migrations/appointments.sql" "$SRV:/root/appointments.sql"
+ssh "$SRV" "sudo -u postgres psql -d dw_unified -v ON_ERROR_STOP=1 -f /root/appointments.sql"
+ssh "$SRV" "sudo -u postgres psql -d dw_unified -tAc \"select count(*) from appt_availability where owner_email='info@designerwallcoverings.com';\"" # → 5
+
+# ─── 2b. Reload app + origin smoke ────────────────────────────────────────────
+ssh "$SRV" "cd $APP && pm2 reload koroseal-quote-api --update-env && sleep 2 && curl -s http://127.0.0.1:3001/api/appointments/health"
+# EXPECT: {"ok":true,"service":"dw-appointments","oauth_configured":false,"availability_rows":5}
+
+# ─── 3. EDGE — api. vhost + SSL (DNS must resolve first) ──────────────────────
+scp "$LOCAL_APPT/deploy/api.designerwallcoverings.com.conf" "$SRV:/etc/nginx/sites-available/api.designerwallcoverings.com"
+ssh "$SRV" "ln -sf /etc/nginx/sites-available/api.designerwallcoverings.com /etc/nginx/sites-enabled/ && nginx -t && systemctl reload nginx"
+ssh "$SRV" "certbot --nginx -d api.designerwallcoverings.com --redirect -m info@designerwallcoverings.com --agree-tos -n"
+# edge smoke:
+curl -s https://api.designerwallcoverings.com/api/appointments/health
+curl -s "https://api.designerwallcoverings.com/api/appointments/availability?owner_email=info@designerwallcoverings.com&date=$(date -v+Mon +%F 2>/dev/null || date -dnextmon +%F)"
+# existing endpoints now reachable (expect JSON validation error, not 404/CORS):
+curl -s -X POST https://api.designerwallcoverings.com/api/sku-inquiry -H 'Content-Type: application/json' -d '{}'
+
+# ─── 4. GOOGLE OAUTH (Steve clicks consent — agents can't) ────────────────────
+# Google Cloud Console → enable Calendar API → OAuth 2.0 Web client.
+# Redirect URI: https://api.designerwallcoverings.com/api/appointments/oauth/callback
+# Route GOOGLE_OAUTH_CLIENT_ID + GOOGLE_OAUTH_CLIENT_SECRET (and ADMIN_PASS) via /secrets → $APP/.env, then:
+ssh "$SRV" "cd $APP && pm2 reload koroseal-quote-api --update-env"
+# Then visit in a browser, signed in as info@'s calendar owner:
+# https://api.designerwallcoverings.com/api/appointments/oauth/start?owner_email=info@designerwallcoverings.com
+curl -s "https://api.designerwallcoverings.com/api/appointments/oauth/status?owner_email=info@designerwallcoverings.com" # → connected:true
+
+# ─── 5. VERIFY ON DEV THEME (no publish) ──────────────────────────────────────
+# Set the appointment-booking section's "Appointments API base URL" (already defaults
+# to https://api.designerwallcoverings.com). Preview the page on the dev theme:
+# https://designer-laboratory-sandbox.myshopify.com/pages/book-appointment?preview_theme_id=144070803507
+# (Needs a Page on template book-appointment to preview — create it as UNPUBLISHED/hidden,
+# or preview the section via the theme editor. Do NOT add it to live nav.)
+
+# ─── 6. HOLD — create live page + nav ……… KEEP IN DEV (do NOT run) ───────────
+# ─── 7. HOLD — publish theme 5.9.0 → live ……… KEEP IN DEV (do NOT run) ────────
+# Run 6–7 only when Steve explicitly says go-live. Until then the feature is fully
+# built + deployed at the api host and previewable on dev, but not customer-visible.
diff --git a/shopify/quote-api/appointments/deploy/api.designerwallcoverings.com.conf b/shopify/quote-api/appointments/deploy/api.designerwallcoverings.com.conf
new file mode 100644
index 00000000..1c0ee138
--- /dev/null
+++ b/shopify/quote-api/appointments/deploy/api.designerwallcoverings.com.conf
@@ -0,0 +1,76 @@
+# ─────────────────────────────────────────────────────────────────────────────
+# api.designerwallcoverings.com — Kamatera nginx vhost (Option A edge routing)
+# Fronts the koroseal-quote-api app (127.0.0.1:3001) for the DW storefront's
+# cross-origin API calls: appointments + the existing koroseal-quote/sku-inquiry.
+#
+# INSTALL (on Kamatera, as root via `ssh my-server`):
+# 1. Put this file at /etc/nginx/sites-available/api.designerwallcoverings.com
+# 2. ln -s /etc/nginx/sites-available/api.designerwallcoverings.com \
+# /etc/nginx/sites-enabled/api.designerwallcoverings.com
+# 3. nginx -t && systemctl reload nginx # serves :80 immediately (for ACME + testing)
+# 4. certbot --nginx -d api.designerwallcoverings.com --redirect -m info@designerwallcoverings.com --agree-tos -n
+# (certbot rewrites this file to add the 443 server + cert paths + the 80→443 redirect)
+#
+# PREREQ: DNS A record api.designerwallcoverings.com → 45.61.58.125 must resolve first.
+# ─────────────────────────────────────────────────────────────────────────────
+
+server {
+ listen 80;
+ listen [::]:80;
+ server_name api.designerwallcoverings.com;
+
+ # Let certbot answer the ACME http-01 challenge.
+ location /.well-known/acme-challenge/ { root /var/www/html; }
+
+ # Reasonable upload ceiling for booking/inquiry payloads.
+ client_max_body_size 256k;
+
+ # ── Appointments API (router sets its own CORS: Allow-Origin *) ──
+ location /api/appointments/ {
+ proxy_pass http://127.0.0.1:3001;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_connect_timeout 10s;
+ proxy_send_timeout 30s;
+ proxy_read_timeout 30s;
+ }
+
+ # ── Appointment confirmation page + .ics (customer-facing, no /api prefix) ──
+ location /appointments/ {
+ proxy_pass http://127.0.0.1:3001;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_connect_timeout 10s;
+ proxy_send_timeout 30s;
+ proxy_read_timeout 30s;
+ }
+
+ # ── Existing DW endpoints (were 404 on the storefront; now reachable here) ──
+ # NOTE: these use the koroseal app's APP-LEVEL cors({origin: CORS_ORIGIN}); set
+ # CORS_ORIGIN in the app .env to the storefront's primary domain (allow apex AND
+ # www if both are served) or the browser blocks the cross-origin POST.
+ location = /api/koroseal-quote {
+ proxy_pass http://127.0.0.1:3001;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
+ location = /api/sku-inquiry {
+ proxy_pass http://127.0.0.1:3001;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
+
+ # Health passthrough (optional convenience).
+ location = /health { proxy_pass http://127.0.0.1:3001; }
+
+ # Everything else on this host: nothing to serve.
+ location / { return 404; }
+}
← 04f34147 Fix sku-inquiry + koroseal-quote 404s: point at api.designer
·
back to Designer Wallcoverings
·
auto-save: 2026-06-29T10:48:32 (5 files) — pending-approval/ bbe051b7 →