# Use the restart script (survives SSH disconnect) root@srv# bash /usr/local/bin/start-aab-api.sh # Verify: root@srv# curl -s http://localhost:3025/api/public-config
◈ SHIFTDECK
READY-TO-LAUNCH
System Status
✓ shiftdeck.tech → Caddy → port 3001
✓ Registry-managed (storectl)
✓ PostgreSQL DB seeded (22 tables, 8 users)
✓ Stripe live mode wired (price IDs set)
✓ Email: Stalwart SMTP hello@shiftdeck.tech
✓ 3-round security review completed
✓ Role gates: requireApprover/requireOwner
✓ Tenant isolation hardened
✓ Build green (162 pages) ✓
⚠ Clock-in race (deferred, needs DB migration)
⚠ Founders cap oversell (deferred)
Manage / Restart
root@srv# storectl restart shiftdeck # Build: root@srv# cd /var/www/shiftdeck/apps/web && npm run build # Status: root@srv# storectl status shiftdeck
── Store Fleet (10 Stores)
STORE NAME
DOMAIN
PORT
STATUS
NOTE
phonecasesforall
phonecasesforall.com
3002
ONLINE
—
>>>phonecasesforcharity
phonecasesforcharity.com
3003
DARK
NO APP DIR — verify path
galaxycaseco
galaxycaseco.com
3005
ONLINE
—
phonecasegift
phonecasegift.com
3006
ONLINE
—
titancase
titancase.com
3007
ONLINE
—
phonecasesforher
phonecasesforher.com
3008
ONLINE
—
casepop
casepop.com
3009
ONLINE
—
phonecaselabs
phonecaselabs.com
3010
ONLINE
dir: /var/www/pcl
inklings
inklings.com
3400
ONLINE
npm run start -p 3400
phonecasesforkids
phonecasesforkids.com
—
STATIC
Caddy serves files, no PM2
⚠ FIX: phonecasesforcharity — No App Directory
1 Verify/create app directory and check registry
# 1. Check what storectl knows root@srv# storectl status phonecasesforcharity # 2. List registered path root@srv# cat /opt/factory/store-registry.json | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('phonecasesforcharity','NOT FOUND'))" # 3a. If dir exists, re-assign and start root@srv# storectl assign phonecasesforcharity /var/www/phonecasesforcharity "npm run start" --port 3003 root@srv# storectl start phonecasesforcharity # 3b. If dir missing — clone/rebuild from base store template root@srv# ls /var/www/ | grep -i charity
2 After fix — verify Caddy reverse proxy block is present for port 3003
root@srv# grep -A2 "phonecasesforcharity" /etc/caddy/Caddyfile root@srv# storectl doctor
── AAB Affiliate Sites (22)
22Sites Live
9GA4 Tagged
13Untracked
GA4-Tagged Sites (9/22)
SITE / SLUG
MEASUREMENT ID
DB ID
ergonomic-office-chairs
G-LKSYTJB6P0
1
best-air-purifiers
G-TQ2VZM34B4
4
camping-gear
G-MMM3KZ3RYY
5
dog-grooming
G-QSX6ZKK49C
6
scentscape
G-HZS86EC466
10
earnvista
G-Y185DBR3G6
11
nibblenook
G-8ZQT2PKVCD
12
drawerlogic
G-ZKT53Z4WJF
13
cookingwise
G-9T979FTR11
14
Backfill Remaining 13 Sites — GA4 Auto-Provision
# Run on VPS — auto-provisions GA4 + rebuilds each site # GA4 admin module: /var/www/aiaffiliate/factory/ga4_admin.py root@srv# cd /var/www/aiaffiliate/factory root@srv# python3 - <<'EOF' import sqlite3, subprocess from ga4_admin import ensure_ga4 db = sqlite3.connect('/opt/factory/phonescale.db') rows = db.execute( "SELECT id, slug, display_title FROM aab_sites WHERE ga4_id IS NULL OR ga4_id=''" ).fetchall() for site_id, slug, title in rows: try: gid = ensure_ga4(slug, title, f"https://{slug}.com") db.execute("UPDATE aab_sites SET ga4_id=? WHERE id=?",(gid,site_id)) db.commit() subprocess.run(["python3","build_site.py",str(site_id)]) print(f"✓ {slug} → {gid}") except Exception as e: print(f"✗ {slug}: {e}") EOF
# Verify all sites now have GA4 IDs root@srv# sqlite3 /opt/factory/phonescale.db \ "SELECT id,slug,ga4_id FROM aab_sites ORDER BY id"
── Email / SMTP (Stalwart)
shiftdeck.tech
✓ LIVE
SMTP HOST127.0.0.1:587 STARTTLS
AUTH USERhello@shiftdeck.tech
STATUS250 2.0.0 Message queued
DKIM✓ Configured
SPF/DMARC✓ Set
ENV VARSMTP_USER=hello@shiftdeck.tech
scopesuite.app
✗ BROKEN
ROOT CAUSEUsing admin auth (501 error)
WRONG AUTHSMTP_USER=admin
FIXUse hello@scopesuite.app
ENV FILE/var/www/scopesuite/.env
NOTEStalwart admin cannot send-as
2-Step Fix
1 Create/verify hello@scopesuite.app mailbox in Stalwart