Skip to main content

AOVIS Direct Store - VM Deployment Preparation

This repository is prepared for a traditional Linux VM deployment path.

Release discipline

Use docs/deploy-sop.md as the formal release baseline.

Default repo entrypoint:

pnpm release:prod

docs/vm-deploy.md covers VM-specific operational details. docs/deploy-sop.md covers:

  • Git as the only valid release source
  • preflight checks
  • scope control
  • post-deploy live truth verification
  • rollback rules

Do not skip the SOP just because the VM commands are familiar.

Why VM now

  • The app is a standard full-stack Next.js project with auth, cart, checkout, orders, and Stripe webhook handling.
  • The Cloudflare Workers/OpenNext path is archived and is not the active deployment path.
  • A VM deployment path keeps the stack aligned with standard Node.js production, simpler ops, and a clear reverse-proxy setup.
  • Ubuntu 22.04 or 24.04
  • Node.js 20 LTS or newer
  • PostgreSQL hosted externally or on the same VM if you want a single-box staging setup
  • Nginx or Caddy as the TLS reverse proxy
  • PM2 as the single recommended process manager

Use the standard Next.js production flow:

  1. npm install
  2. npm run build
  3. npm run start

For VM deployment, this is the least surprising path and keeps the runtime aligned with the current codebase.

Operator SSH entrypoint

When deploying from the operator workstation, use gcloud compute ssh rather than a handwritten ssh user@host command. This keeps the deployment path aligned with the project's actual GCP setup and avoids SSH agent / manual key drift.

The local Google Compute Engine key is typically:

  • ~/.ssh/google_compute_engine

In the current operator environment this resolves to:

  • /Users/guorui/.ssh/google_compute_engine

If you are already on the VM, do not use this local key path; just run the in-VM Git / build / PM2 commands below.

External AI / Automation Guidance

If an external AI tool or automation runner needs to deploy this repo, it must not guess a private key path or assume an SSH agent is already loaded.

Use this operator-owned command path instead:

gcloud compute ssh aovis-store-staging-vm --zone us-west1-c --project aovis-site-1 --command 'cd /opt/aovis/aovis-store-staging && git fetch origin && git pull origin main && npm install && npx prisma generate && npm run build && pm2 restart aovis-store-staging --update-env'

Rules for automation tools:

  • Do not require a manual ssh user@host step.
  • Do not require the tool to read or manage the operator workstation private key.
  • Do not overwrite /opt/aovis/aovis-store-staging/.env.production.
  • If the tool cannot invoke gcloud compute ssh, it should stop and ask the operator to run the command rather than inventing an alternate SSH path.
  • If the tool is already inside the VM shell, it may use the in-VM Git / build / PM2 sequence directly.

Current deployment scripts

  • npm run build
  • npm run start
  • npm run dev
  • Prisma commands remain available for schema and seed workflows

Environment variables

Must have for staging

  • NEXT_PUBLIC_APP_URL
  • DATABASE_URL
  • AUTH_SECRET
  • STRIPE_SECRET_KEY
  • NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY

Add after webhook setup

  • STRIPE_WEBHOOK_SECRET

Can stay unset for initial staging

  • AUTH_GOOGLE_ID
  • AUTH_GOOGLE_SECRET
  • AUTH_APPLE_ID
  • AUTH_APPLE_SECRET
  • AUTH_APPLE_TEAM_ID
  • AUTH_APPLE_KEY_ID
  • AUTH_EMAIL_SERVER
  • AUTH_EMAIL_FROM

If you enable Apple sign-in on the VM, provide all four Apple variables together. AUTH_APPLE_SECRET should hold the raw .p8 private key material, with literal newlines escaped as \n when required by the deployment environment.

Apple sign-in recovery checklist

If Apple sign-in disappears after a deploy or UI change, do this in order:

  1. Check the VM env file:
    • grep '^AUTH_APPLE_' /opt/aovis/aovis-store-staging/.env.production
  2. Confirm the provider list:
    • curl -s https://aovis.app/api/auth/providers
  3. Restore these variables together if missing:
    • AUTH_URL=https://aovis.app
    • NEXTAUTH_URL=https://aovis.app
    • NEXT_PUBLIC_APP_URL=https://aovis.app
    • AUTH_TRUST_HOST=true
    • AUTH_APPLE_ID=app.aovis.web
    • AUTH_APPLE_TEAM_ID=9UU8NX66DJ
    • AUTH_APPLE_KEY_ID=LD35DMBTKG
    • AUTH_APPLE_SECRET=...
  4. Restart PM2 with updated env:
    • pm2 restart aovis-store-staging --update-env
  5. Verify again:
    • /api/auth/providers must include apple
    • /signin must show Apple as enabled

Safe restore method

Use a file-editing step on the VM, not a bulk repo sync:

  • read the current .env.production
  • update or append the Apple block in place
  • keep AUTH_APPLE_SECRET as a single line with literal \n escapes
  • restart PM2 with --update-env

Do not bulk-sync or overwrite .env.production during code deployment. Keep it as a separately managed VM secret file.

Incident summary

This repo already hit an Apple sign-in outage caused by VM environment drift:

  • the live app VM lost AUTH_APPLE_*
  • /api/auth/providers stopped returning apple
  • the sign-in UI showed Apple as unavailable

That outage was caused by deployment/environment overwrite, not by the OAuth UI itself.

Suggested .env.production

Use the same variable names as local development, but put the staging values into the VM environment or a local .env.production file on the server. Do not commit secrets to git.

Process management

Use PM2 as the process manager for this repo on a single VM:

  • restart on crash
  • boot on server restart
  • keep logs available through pm2 logs

Suggested command:

PORT=3000 npm run start

Then place Nginx or Caddy in front of it.

PM2 handles:

  • restart on crash
  • boot on server restart
  • log access through pm2 logs

Git deploy key setup

The app VM is expected to pull code from the repo over Git SSH, not by storing a personal GitHub token.

Deploy key location on the VM

  • Private key: /opt/aovis/.ssh/github_deploy_key
  • Public key: /opt/aovis/.ssh/github_deploy_key.pub
  • SSH config: /opt/aovis/.ssh/config

This is separate from the operator workstation's Google Compute SSH key.

Recommended SSH config:

Host github.com
HostName github.com
User git
IdentityFile /opt/aovis/.ssh/github_deploy_key
IdentitiesOnly yes

Rotation procedure

  1. Generate a new VM-local read-only key:
    • ssh-keygen -t ed25519 -C "aovis-store-staging-vm-readonly" -f /opt/aovis/.ssh/github_deploy_key -N ""
  2. Copy the new public key from:
    • /opt/aovis/.ssh/github_deploy_key.pub
  3. Add it to the GitHub repository as a read-only deploy key
  4. Remove the old deploy key from GitHub
  5. Verify on the VM:
    • cd /opt/aovis/aovis-store-staging
    • git fetch origin
    • git pull origin main

Do not store personal GitHub tokens with write access on the VM.

Reverse proxy

Put Nginx or Caddy in front of the Next.js process for:

  • HTTPS
  • domain routing
  • gzip / brotli
  • optional caching headers

The proxy should forward to the local app port, typically 127.0.0.1:3000.

Historical staging domain examples

These are archived references from earlier validation passes and should not be copied into live environment variables or reverse-proxy config:

  • store-staging.aovis.app
  • shop-staging.aovis.app
  • app-staging.aovis.app

For the current live path, keep the VM and proxy aligned to https://aovis.app.

Stripe notes

  • Staging can continue using Stripe test keys.
  • Keep the webhook endpoint at /api/stripe/webhook.
  • The staging webhook URL should look like https://<staging-domain>/api/stripe/webhook.
  • The local stripe listen whsec is only for local debugging.
  • Staging and production webhook secrets must be managed separately.

Database notes

The app continues to use Prisma with PostgreSQL in the standard Node runtime.

For VM deployment:

  • keep PostgreSQL reachable from the VM
  • do not change the database product in this pass
  • do not introduce Cloudflare-specific runtime assumptions

Current limits

  • This pass does not add new business scope.
  • Device binding, entitlement automation, Pangolin integration, and new product logic remain out of scope.
  • Stripe live mode is a later step after staging validation.