Deploy on DigitalOcean App Platform (GitHub-source build) + consolidate audit-fixes
Deploy config: - .do/app.yaml: build the Dockerfile directly from GitHub (deploy_on_push) instead of a pre-built DOCR image; NEXT_PUBLIC_* set RUN_AND_BUILD_TIME with the propertymanagement.network domain so they bake into the client bundle; add custom domains block (apex + www); wire Sentry DSN (server + browser). Included pending work from the audit-fixes branch: - AI provider abstraction (OpenAI/Anthropic, admin-selectable; Anthropic default) - Per-landlord e-signature (DocuSign OAuth + Dropbox Sign) + migration 0010 - Outbound webhooks / Zapier integration - PayPal removal (Stripe-only billing) - Storage hardening (fail-loud when Spaces unconfigured), security fixes Verified: full production Docker build (same build-args as DO) passes clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
917a06ee85
commit
5495b94924
+91
-47
@@ -1,30 +1,28 @@
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# DigitalOcean App Platform spec — Property Management Network
|
||||
#
|
||||
# Deploy: doctl apps create --spec .do/app.yaml
|
||||
# Deploy: doctl apps create --spec .do/app.yaml (or the DO MCP apps-create)
|
||||
# Update: doctl apps update <APP_ID> --spec .do/app.yaml
|
||||
#
|
||||
# SOURCE: image-based from DigitalOcean Container Registry (DOCR). The app's git
|
||||
# lives on self-hosted Gitea, which App Platform cannot pull, so we build the
|
||||
# Docker image ourselves and push it to DOCR. See DIGITALOCEAN.md for the full
|
||||
# build/push/deploy walkthrough.
|
||||
# SOURCE: App Platform builds the Dockerfile directly from GitHub
|
||||
# (github.com/silkoserfo/property-management-network). Pushes to `main`
|
||||
# auto-redeploy (deploy_on_push). No DOCR image build/push needed.
|
||||
#
|
||||
# SECRETS: values marked `type: SECRET` are placeholders — set the real values in
|
||||
# the App Platform dashboard (App → Settings → Environment Variables) or via
|
||||
# `doctl`. Never commit real secrets to this file.
|
||||
# the App Platform dashboard (App → Settings → Environment Variables) or via the
|
||||
# create spec. Never commit real secrets to this file.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
name: property-management-network
|
||||
region: nyc
|
||||
|
||||
services:
|
||||
- name: web
|
||||
# Pre-built image pushed to DOCR (repository must exist in your registry).
|
||||
image:
|
||||
registry_type: DOCR
|
||||
repository: property-management-network
|
||||
tag: latest
|
||||
deploy_on_push:
|
||||
enabled: true
|
||||
# Built by App Platform from GitHub using the repo Dockerfile.
|
||||
github:
|
||||
repo: silkoserfo/property-management-network
|
||||
branch: main
|
||||
deploy_on_push: true
|
||||
dockerfile_path: Dockerfile
|
||||
instance_count: 1
|
||||
instance_size_slug: apps-s-1vcpu-1gb
|
||||
http_port: 3000
|
||||
@@ -37,32 +35,35 @@ services:
|
||||
failure_threshold: 3
|
||||
envs:
|
||||
# ── App URLs ──────────────────────────────────────────────────────────
|
||||
# ${APP_URL} resolves to the app's public URL at runtime. NOTE: the client
|
||||
# bundle bakes NEXT_PUBLIC_APP_URL at *image build* time (see Dockerfile /
|
||||
# DIGITALOCEAN.md), so build the image with the same URL you serve on.
|
||||
# NEXT_PUBLIC_* are inlined into the client bundle at BUILD time, so they
|
||||
# must be RUN_AND_BUILD_TIME with the literal domain we serve on.
|
||||
- key: NEXT_PUBLIC_APP_URL
|
||||
scope: RUN_TIME
|
||||
value: ${APP_URL}
|
||||
scope: RUN_AND_BUILD_TIME
|
||||
value: https://propertymanagement.network
|
||||
- key: BETTER_AUTH_URL
|
||||
scope: RUN_TIME
|
||||
value: ${APP_URL}
|
||||
value: https://propertymanagement.network
|
||||
- key: NEXT_PUBLIC_APP_NAME
|
||||
scope: RUN_TIME
|
||||
scope: RUN_AND_BUILD_TIME
|
||||
value: Property Management Network
|
||||
|
||||
# ── Database (managed Postgres — use the PRIVATE host; see DIGITALOCEAN.md) ──
|
||||
# ── Admin & auth policy ───────────────────────────────────────────────
|
||||
- key: ADMIN_EMAILS
|
||||
scope: RUN_TIME
|
||||
value: leon@phluit.com
|
||||
- key: REQUIRE_EMAIL_VERIFICATION
|
||||
scope: RUN_TIME
|
||||
value: "true"
|
||||
|
||||
# ── Database (managed Postgres — PRIVATE host, direct port 25060) ──
|
||||
- key: DATABASE_URL
|
||||
scope: RUN_TIME
|
||||
type: SECRET
|
||||
value: REPLACE_IN_DASHBOARD
|
||||
# Verified TLS (encrypted + certificate-checked). DO Managed Postgres uses
|
||||
# a CA that isn't in the system trust store, so paste the cluster's CA cert
|
||||
# into DATABASE_CA: DO control panel → Database → Connection Details →
|
||||
# "Download CA certificate", then paste its PEM contents as the DATABASE_CA
|
||||
# secret in the App Platform dashboard. Without a valid CA the app will
|
||||
# refuse to connect (fail loud) rather than run unverified.
|
||||
# Emergency fallback ONLY (not for production): DATABASE_SSL=no-verify is
|
||||
# encrypted but does NOT verify the server certificate.
|
||||
# Verified TLS: DO's Managed Postgres CA isn't in the system trust store, so
|
||||
# paste the cluster CA PEM (repo root ca-certificate.crt) into DATABASE_CA.
|
||||
# With `require` + a valid CA the app connects verified; without a valid CA
|
||||
# it fails loud rather than run unverified.
|
||||
- key: DATABASE_SSL
|
||||
scope: RUN_TIME
|
||||
value: require
|
||||
@@ -70,8 +71,7 @@ services:
|
||||
scope: RUN_TIME
|
||||
type: SECRET
|
||||
value: REPLACE_IN_DASHBOARD
|
||||
# Schema is migrated out-of-band (as doadmin), NOT on boot — the app user
|
||||
# intentionally lacks DDL rights. Keep this false; run migrations manually.
|
||||
# Schema is migrated out-of-band (as doadmin), NOT on boot.
|
||||
- key: RUN_MIGRATIONS_ON_START
|
||||
scope: RUN_TIME
|
||||
value: "false"
|
||||
@@ -81,14 +81,15 @@ services:
|
||||
scope: RUN_TIME
|
||||
type: SECRET
|
||||
value: REPLACE_IN_DASHBOARD
|
||||
# Google OAuth (optional — leave blank to disable the Google button).
|
||||
- key: GOOGLE_CLIENT_ID
|
||||
scope: RUN_TIME
|
||||
type: SECRET
|
||||
value: REPLACE_IN_DASHBOARD
|
||||
value: ""
|
||||
- key: GOOGLE_CLIENT_SECRET
|
||||
scope: RUN_TIME
|
||||
type: SECRET
|
||||
value: REPLACE_IN_DASHBOARD
|
||||
value: ""
|
||||
|
||||
# ── Stripe ────────────────────────────────────────────────────────────
|
||||
- key: STRIPE_SECRET_KEY
|
||||
@@ -99,22 +100,21 @@ services:
|
||||
scope: RUN_TIME
|
||||
type: SECRET
|
||||
value: REPLACE_IN_DASHBOARD
|
||||
# No STRIPE_*_PRICE_ID vars — prices are resolved by lookup key and
|
||||
# auto-created on first checkout (lib/stripe/prices.ts). Going live only
|
||||
# needs the two live secrets above + the live publishable key below.
|
||||
|
||||
# ── OpenAI ────────────────────────────────────────────────────────────
|
||||
- key: OPENAI_API_KEY
|
||||
# ── AI provider (Anthropic default; OpenAI optional) ──────────────────
|
||||
- key: ANTHROPIC_API_KEY
|
||||
scope: RUN_TIME
|
||||
type: SECRET
|
||||
value: REPLACE_IN_DASHBOARD
|
||||
- key: ANTHROPIC_MODEL
|
||||
scope: RUN_TIME
|
||||
value: claude-haiku-4-5
|
||||
- key: OPENAI_API_KEY
|
||||
scope: RUN_TIME
|
||||
type: SECRET
|
||||
value: ""
|
||||
|
||||
# ── Email (SMTP — SMTP2GO) ────────────────────────────────────────────
|
||||
# The app sends mail via SMTP only (nodemailer). Email is silently skipped
|
||||
# unless SMTP_HOST + SMTP_USER + SMTP_PASS are all set — password resets,
|
||||
# email verification, rent/overdue/lease reminders, team invites, and
|
||||
# payment links all depend on this. EMAIL_FROM is a bare address; the app
|
||||
# wraps it as "Property Management Network <…>".
|
||||
- key: SMTP_HOST
|
||||
scope: RUN_TIME
|
||||
value: mail.smtp2go.com
|
||||
@@ -133,10 +133,9 @@ services:
|
||||
scope: RUN_TIME
|
||||
value: postmaster@propertymanagement.network
|
||||
|
||||
# ── Cloudflare Turnstile (site key is public; baked into the client bundle
|
||||
# at image build time — keep it in sync when you build) ──
|
||||
# ── Cloudflare Turnstile (site key public; baked at build time) ──
|
||||
- key: NEXT_PUBLIC_TURNSTILE_SITE_KEY
|
||||
scope: RUN_TIME
|
||||
scope: RUN_AND_BUILD_TIME
|
||||
value: 0x4AAAAAADuDQverznfv1a60
|
||||
- key: TURNSTILE_SECRET_KEY
|
||||
scope: RUN_TIME
|
||||
@@ -165,8 +164,53 @@ services:
|
||||
scope: RUN_TIME
|
||||
value: https://nyc3.cdn.digitaloceanspaces.com
|
||||
|
||||
# ── Accounting sync (optional — per-landlord QuickBooks / Xero OAuth) ──
|
||||
- key: QBO_CLIENT_ID
|
||||
scope: RUN_TIME
|
||||
type: SECRET
|
||||
value: ""
|
||||
- key: QBO_CLIENT_SECRET
|
||||
scope: RUN_TIME
|
||||
type: SECRET
|
||||
value: ""
|
||||
- key: QBO_ENVIRONMENT
|
||||
scope: RUN_TIME
|
||||
value: production
|
||||
- key: XERO_CLIENT_ID
|
||||
scope: RUN_TIME
|
||||
type: SECRET
|
||||
value: ""
|
||||
- key: XERO_CLIENT_SECRET
|
||||
scope: RUN_TIME
|
||||
type: SECRET
|
||||
value: ""
|
||||
- key: XERO_SALES_ACCOUNT_CODE
|
||||
scope: RUN_TIME
|
||||
value: "200"
|
||||
- key: XERO_EXPENSE_ACCOUNT_CODE
|
||||
scope: RUN_TIME
|
||||
value: "400"
|
||||
|
||||
# ── Error monitoring (Sentry — DSN is public; browser DSN baked at build) ──
|
||||
- key: SENTRY_DSN
|
||||
scope: RUN_TIME
|
||||
value: https://ef6aa585a080711e14a855b6cc024e9a@o4509830676873216.ingest.us.sentry.io/4511667160219648
|
||||
- key: NEXT_PUBLIC_SENTRY_DSN
|
||||
scope: RUN_AND_BUILD_TIME
|
||||
value: https://ef6aa585a080711e14a855b6cc024e9a@o4509830676873216.ingest.us.sentry.io/4511667160219648
|
||||
- key: SENTRY_ENVIRONMENT
|
||||
scope: RUN_TIME
|
||||
value: production
|
||||
|
||||
# ── Cron (Bearer token the DO Function sends to /api/cron/*) ──
|
||||
- key: CRON_SECRET
|
||||
scope: RUN_TIME
|
||||
type: SECRET
|
||||
value: REPLACE_IN_DASHBOARD
|
||||
|
||||
# ── Custom domains (DNS hosted on Cloudflare — set CNAMEs there, DNS-only) ──
|
||||
domains:
|
||||
- domain: propertymanagement.network
|
||||
type: PRIMARY
|
||||
- domain: www.propertymanagement.network
|
||||
type: ALIAS
|
||||
|
||||
Reference in New Issue
Block a user