Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions DEPLOY.md
Original file line number Diff line number Diff line change
@@ -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-<campaign> -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).
16 changes: 16 additions & 0 deletions deploy/directory.env
Original file line number Diff line number Diff line change
@@ -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=
16 changes: 16 additions & 0 deletions deploy/national.env
Original file line number Diff line number Diff line change
@@ -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=
50 changes: 50 additions & 0 deletions deploy/nginx/salon-platform.conf
Original file line number Diff line number Diff line change
@@ -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;
}
}
20 changes: 20 additions & 0 deletions deploy/orlando.env
Original file line number Diff line number Diff line change
@@ -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=
21 changes: 21 additions & 0 deletions deploy/systemd/salon-directory.service
Original file line number Diff line number Diff line change
@@ -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
21 changes: 21 additions & 0 deletions deploy/systemd/salon-national.service
Original file line number Diff line number Diff line change
@@ -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
24 changes: 24 additions & 0 deletions deploy/systemd/salon-orlando.service
Original file line number Diff line number Diff line change
@@ -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
Loading