diff --git a/DEPLOY.md b/DEPLOY.md new file mode 100644 index 0000000..b048190 --- /dev/null +++ b/DEPLOY.md @@ -0,0 +1,76 @@ +# Deploy — three storefronts, one box + +One build, three processes, nginx maps domain → port. Everything below is +idempotent; re-running a step never breaks a working deployment. + +``` +aveda-salon-orlando.com → :3201 (SALON_CAMPAIGN=orlando) +aveda-salon.com → :3202 (SALON_CAMPAIGN=national) +salon-near-me.com → :3203 (SALON_CAMPAIGN=directory) + all → nedbd :7070 (NEDB_DB=salon, token-gated, encrypted) +``` + +## Prerequisites + +- Node 20+, npm +- A running **nedbd** (`pip install nedb-engine`) with `NEDBD_TOKEN` set and + `NEDB_TMK` encryption on — the engine daemon already serving this VPS works; + the platform only adds a database (`salon`), not a service +- DNS: all three domains on Cloudflare, orange cloud on +- TLS posture (locked decision 4, 2026-07-07): **Cloudflare Flexible** — + edge terminates TLS, edge→origin is port 80, matching the existing VPS. + Full (Strict) + origin certs is queued hardening; when flipped, add 443 + server blocks to `deploy/nginx/salon-platform.conf` + +## First deploy + +```bash +cd ~ && git clone https://github.com/Eth-Interchained/salon-platform.git +cd salon-platform +npm ci +npm run build # ONE build serves all three + +# per-campaign env: fill REPLACE_ME (NEDB_TOKEN, SALON_ADMIN_TOKEN) +$EDITOR deploy/orlando.env deploy/national.env deploy/directory.env + +# systemd (adjust WorkingDirectory/EnvironmentFile paths if not /root) +sudo cp deploy/systemd/salon-*.service /etc/systemd/system/ +sudo systemctl daemon-reload +sudo systemctl enable --now salon-orlando salon-national salon-directory + +# nginx — through Mail-in-a-Box's custom config hook (MIAB manages nginx; +# never drop files into sites-enabled directly) +sudo cp deploy/nginx/salon-platform.conf /etc/nginx/conf.d/salon-platform.conf +sudo nginx -t && sudo systemctl reload nginx +``` + +## Verify (never claim green without this) + +```bash +for p in 3201 3202 3203; do curl -s localhost:$p/api/health | python3 -m json.tool | head -8; done +curl -s localhost:3201/robots.txt # campaign-driven, sitemap line on its own origin +curl -s localhost:3202/sitemap.xml # honest skeleton — homepage only +# then from outside: each domain's / shows ITS brand — the boot banner and +# journalctl -u salon- -f name the campaign in every log line +``` + +## Update + +```bash +cd ~/salon-platform && git pull origin main +npm ci && npm run build +sudo systemctl restart salon-orlando salon-national salon-directory +for p in 3201 3202 3203; do curl -sf localhost:$p/api/health >/dev/null && echo ":$p ok"; done +``` + +## Rollback + +- **Code**: `git revert` the merge on main (never force-push), redeploy as above. +- **Content** (Phase 1+): engine history is the rollback — `AS OF` any document's + prior version and re-put; nothing is ever lost. + +## Blast radius + +Each storefront is its own unit: a bad restart of one leaves the other two +serving. The engine is shared by design (one database, campaign-tagged +content, cross-entity DAG edges). diff --git a/deploy/directory.env b/deploy/directory.env new file mode 100644 index 0000000..e7bf3f8 --- /dev/null +++ b/deploy/directory.env @@ -0,0 +1,16 @@ +# Portal Salon Platform — directory storefront (salon-near-me.com) +# Systemd EnvironmentFile. Copy beside the checkout, fill REPLACE_ME values. + +NODE_ENV=production +SALON_CAMPAIGN=directory +PUBLIC_ORIGIN=https://salon-near-me.com +SALON_API_PORT=3203 + +NEDB_URL=http://127.0.0.1:7070 +NEDB_DB=salon +NEDB_TOKEN=REPLACE_ME + +SALON_ADMIN_TOKEN=REPLACE_ME + +# AIASSIST_BASE_URL=https://api.aiassist.net +# AIASSIST_API_KEY= diff --git a/deploy/national.env b/deploy/national.env new file mode 100644 index 0000000..840caea --- /dev/null +++ b/deploy/national.env @@ -0,0 +1,16 @@ +# Portal Salon Platform — national storefront (aveda-salon.com) +# Systemd EnvironmentFile. Copy beside the checkout, fill REPLACE_ME values. + +NODE_ENV=production +SALON_CAMPAIGN=national +PUBLIC_ORIGIN=https://aveda-salon.com +SALON_API_PORT=3202 + +NEDB_URL=http://127.0.0.1:7070 +NEDB_DB=salon +NEDB_TOKEN=REPLACE_ME + +SALON_ADMIN_TOKEN=REPLACE_ME + +# AIASSIST_BASE_URL=https://api.aiassist.net +# AIASSIST_API_KEY= diff --git a/deploy/nginx/salon-platform.conf b/deploy/nginx/salon-platform.conf new file mode 100644 index 0000000..3dacb7a --- /dev/null +++ b/deploy/nginx/salon-platform.conf @@ -0,0 +1,50 @@ +# Portal Salon Platform — three storefronts, one box. +# Cloudflare sits in front (orange cloud). TLS posture per locked decision 4 +# (2026-07-07): FLEXIBLE for now — Cloudflare terminates TLS, edge→origin is +# port 80, matching the existing VPS posture. Full (Strict) + origin certs +# stays on the hardening queue; when flipped, add the 443 blocks here. +# +# Mail-in-a-Box manages nginx on this VPS: install through MIAB's custom +# config hook (conf.d include), NOT by dropping files into sites-enabled. + +server { + listen 80; + server_name aveda-salon-orlando.com www.aveda-salon-orlando.com; + + location / { + proxy_pass http://127.0.0.1:3201; + 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 https; + proxy_read_timeout 60s; + } +} + +server { + listen 80; + server_name aveda-salon.com www.aveda-salon.com; + + location / { + proxy_pass http://127.0.0.1:3202; + 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 https; + proxy_read_timeout 60s; + } +} + +server { + listen 80; + server_name salon-near-me.com www.salon-near-me.com; + + location / { + proxy_pass http://127.0.0.1:3203; + 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 https; + proxy_read_timeout 60s; + } +} diff --git a/deploy/orlando.env b/deploy/orlando.env new file mode 100644 index 0000000..ed8c4db --- /dev/null +++ b/deploy/orlando.env @@ -0,0 +1,20 @@ +# Portal Salon Platform — orlando storefront (aveda-salon-orlando.com) +# Systemd EnvironmentFile. Copy beside the checkout, fill REPLACE_ME values. +# NEVER commit real tokens — this file is a template. + +NODE_ENV=production +SALON_CAMPAIGN=orlando +PUBLIC_ORIGIN=https://aveda-salon-orlando.com +SALON_API_PORT=3201 + +NEDB_URL=http://127.0.0.1:7070 +NEDB_DB=salon +# Must match NEDBD_TOKEN on the daemon (token-gated in production) +NEDB_TOKEN=REPLACE_ME + +# Write gate until the admin dashboard lands (Phase 5) +SALON_ADMIN_TOKEN=REPLACE_ME + +# AI content pipeline (Phase 3) — leave unset until then +# AIASSIST_BASE_URL=https://api.aiassist.net +# AIASSIST_API_KEY= diff --git a/deploy/systemd/salon-directory.service b/deploy/systemd/salon-directory.service new file mode 100644 index 0000000..15c6aec --- /dev/null +++ b/deploy/systemd/salon-directory.service @@ -0,0 +1,21 @@ +# Portal Salon Platform — directory storefront +# Install: cp deploy/systemd/salon-directory.service /etc/systemd/system/ +# systemctl daemon-reload && systemctl enable --now salon-directory + +[Unit] +Description=Portal Salon Platform — directory (salon-near-me.com :3203) +After=network.target +Wants=network-online.target + +[Service] +Type=simple +WorkingDirectory=/root/salon-platform +EnvironmentFile=/root/salon-platform/deploy/directory.env +ExecStart=/usr/bin/env npm run start +Restart=on-failure +RestartSec=3 +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target diff --git a/deploy/systemd/salon-national.service b/deploy/systemd/salon-national.service new file mode 100644 index 0000000..ea90de9 --- /dev/null +++ b/deploy/systemd/salon-national.service @@ -0,0 +1,21 @@ +# Portal Salon Platform — national storefront +# Install: cp deploy/systemd/salon-national.service /etc/systemd/system/ +# systemctl daemon-reload && systemctl enable --now salon-national + +[Unit] +Description=Portal Salon Platform — national (aveda-salon.com :3202) +After=network.target +Wants=network-online.target + +[Service] +Type=simple +WorkingDirectory=/root/salon-platform +EnvironmentFile=/root/salon-platform/deploy/national.env +ExecStart=/usr/bin/env npm run start +Restart=on-failure +RestartSec=3 +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target diff --git a/deploy/systemd/salon-orlando.service b/deploy/systemd/salon-orlando.service new file mode 100644 index 0000000..e329a71 --- /dev/null +++ b/deploy/systemd/salon-orlando.service @@ -0,0 +1,24 @@ +# Portal Salon Platform — orlando storefront +# Install: cp deploy/systemd/salon-orlando.service /etc/systemd/system/ +# systemctl daemon-reload && systemctl enable --now salon-orlando +# NOTE: adjust WorkingDirectory + EnvironmentFile to the checkout path, and +# ExecStart to the box's node install if npm isn't on the system PATH. + +[Unit] +Description=Portal Salon Platform — orlando (aveda-salon-orlando.com :3201) +After=network.target +Wants=network-online.target + +[Service] +Type=simple +WorkingDirectory=/root/salon-platform +EnvironmentFile=/root/salon-platform/deploy/orlando.env +ExecStart=/usr/bin/env npm run start +Restart=on-failure +RestartSec=3 +# stdout carries the color-coded request log; journalctl -u salon-orlando -f +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target