Appearance
WL multi-version hosting — briefing for the WL server team
Purpose: we want to run more than one version of the WL product at the same time, and let the admin panel pick, per domain, which version serves it — so connecting/switching a domain also switches its version.
Who does what today:
128.199.72.46(this box) — TogoActive admin only (admin-frontend + admin-backend,v3.togoactive.com). We own this.178.128.113.107— the WL host. Runs the public WL site, the WL API, and the deploy agent. We do not have this server's code or access. Everything below marked ❓ is a question for you.
Shared between both: one database (DigitalOcean togoactive).
Part A — What the admin side does today (verified in our code)
1. Domain connect flow
| Step | Where |
|---|---|
| Admin enters domain in Setup → Domain | admin-frontend/src/components/event-manage/setup/SetupDomain.jsx |
POST /api/v1/events/{id}/domain → creates event_domains row, status=pending, TXT token, a_record_ip | DomainController::store |
| Admin adds TXT + A record at their DNS provider | customer side |
POST .../domain/{domainId}/verify-txt then verify-dns → status=active | DomainController |
| On activate / disconnect → fire deploy webhook | App\Services\WlDeployService::syncDomains() |
2. What we call on your server
POST https://wl-deploy.togoparts.com/sync-domains
Authorization: Bearer <WL_DEPLOY_TOKEN>
(no body)Treated as best-effort: HTTP 2xx or 409 = success; anything else becomes a non-blocking warning in the admin UI with a "Retry sync" button. Timeouts: 5s connect / 20s total.
Our deploy.sh also uses: POST /deploy, POST /deploy/web, POST /deploy/api, GET /health. /health currently answers {"ok":true,"running":null}.
3. event_domains table (shared DB — you read this)
id, event_id, domain, status enum(pending|txt_verified|active|failed),
txt_record_name, txt_record_value, a_record_ip,
txt_verified_at, dns_verified_at, last_checked_at, created_at, updated_at
UNIQUE(domain) -- one host → exactly one event
INDEX(event_id) -- unique(event_id) was DROPPED: an event may have MANY domainsLive rows today (all status=active):
| event | domain |
|---|---|
| 46 | cyclehome2027-v3.togoparts.com |
| 49 | togosg61.togoparts.com |
| 50 | strideforcare.sg |
| 52 | moveforgood.togoparts.com |
a_record_ip is written from admin-backend config SERVER_IP=178.128.113.107; verify-dns accepts any IP in SERVER_IPS=178.128.113.107,128.199.72.46.
4. How a request finds its event at runtime
frontend-wl-development/middleware.ts (Edge) reads the Host header → calls WL API GET /api/v1/resolve-event?host=<host> → looks up event_domains where status=active → forwards x-resolved-event-id to the app. EVENT_ID env, when set, overrides it.
Caching to be aware of when switching: resolve-event sends Cache-Control: public, max-age=60, and the middleware keeps its own 60s in-memory host→event cache.
5. What "a version" means for us
The candidate new version (v4, category-based paid registration) is the same two repos on a different branch:
| repo | v3 | v4 |
|---|---|---|
togoparts/tga-v3-wl-web (Next.js) | main | v4 |
togoparts/tga-v3-wl-api (Laravel) | main | v4 |
So "version" ≈ "branch + its own running instance". Not a fork, not a different product.
Part B — What we can see about your server from outside (verified)
wl-api.togoparts.com,wl-deploy.togoparts.com, and every customer domain all resolve to178.128.113.107.Server: Apache/2.4.41 (Ubuntu),X-Powered-By: Next.js→ Apache reverse-proxies into a Next.js process.- Our Next.js middleware comments say the app sits behind Apache and that Apache terminates TLS and does not send
X-Forwarded-Proto. - One deployment appears to serve every customer domain (multi-tenant, host-resolved) — no per-event build.
Part C — Questions for you ❓
C1. The deploy agent (wl-deploy.togoparts.com)
- Where does its source live (repo/path)? Can we get read access, or would you make the changes yourself?
- What exactly does
POST /sync-domainsdo — does it readevent_domainsfrom the shared DB, or does it get the list some other way? - What does it generate per domain: an Apache vhost file from a template? Where do those files live? Is
certbotrun per domain? - What do
/deploy,/deploy/web,/deploy/apido step by step (git pull → build → restart what)? - Is there any concurrency lock (the 409 we treat as "already running")?
- Is it possible to pass a body/params to these endpoints today, or are they strictly parameterless?
C2. Current runtime layout
- How is the Next.js app run — pm2, systemd, docker? Which port? Which directory?
- How is the WL API run — php-fpm pool, its own vhost? Which directory, which
.env? - Is there exactly one web process and one API process serving all domains today? (We believe yes — please confirm.)
- How does the Apache vhost template look for one customer domain (a sanitised sample would help a lot)?
C3. Feasibility of the plan
- Can we run a second web instance on a second port (e.g. v3 :3000, v4 :3100) on the same server — RAM/CPU headroom OK?
- Can we run a second WL API (separate php-fpm pool + own
.env/storage/config cache) alongside the current one? - Can the vhost template become per-domain conditional — i.e. the
proxy_passupstream chosen from aversionvalue we store on theevent_domainsrow? - Would you rather the agent read
versionfrom the DB itself, or have us send it in the/sync-domainsrequest? - Can
/deploy/weband/deploy/apitake aversion(branch + target directory + which process to restart), so deploying v4 does not touch v3? - Any constraint that forces a new version onto a different server instead of a different port? (Big deal for us — see the warning in Part E.)
C4. Operations
- How are SSL certs managed — certbot per domain, auto-renew cron? Does changing a vhost's upstream affect the cert at all?
- What is the rollback story if a vhost regenerates badly — is there a backup of the previous vhost set?
- Where do the agent's logs go? Can we see why a
/sync-domainsfailed? - Any staging/test hostname we can point at a v4 instance without touching a customer domain?
Part D — The design we have in mind (react to this, please)
Per-domain version, chosen in the admin, switchable without DNS changes.
DB — new lookup table so a new version never needs a migration:
wl_versions(id, key, label, branch, web_port, api_base_url, is_default, is_active) 'v3' | main | 3000 'v4' | v4 | 3100 event_domains.version_id (FK, default = the is_default row)Existing 4 domains backfill to
v3→ zero behaviour change on day one.Admin — version dropdown on the connect form; version badge per domain row; new
PATCH /events/{id}/domain/{domainId}/versionthat changes the value and then fires the existingsync-domainswebhook. That is the switch.Your agent — vhost generation becomes version-aware: each domain's
proxy_passpoints at its version's upstream port. Deploy endpoints take a version so each instance updates independently.Your server — one checkout + one port + one process per version, for both web and API. Each version's web
.envpointsAPI_BASE_URLat its own API.Optional safety net —
/resolve-eventalso returns the version, so an instance that receives a request meant for another version can show a clear error instead of silently serving the wrong product.
Rollout order: DB columns (no-op) → stand up v4 instance with no domains → agent changes → admin UI → migrate one real domain. Rollback = switch the version back and re-sync.
Part E — Two things we need agreed
1. All versions on the SAME server, different ports.a_record_ip today is a single fixed IP (178.128.113.107) baked into the admin config, and it is what customers put in their DNS. If a version lives on a different server, switching a domain's version means the customer must change their DNS A record — no instant switch, no instant rollback, and SSL has to be re-issued. Different ports on one host keeps a switch to "rewrite vhost + reload", i.e. seconds.
2. Database compatibility rule. v3 and v4 share one database. Any v4 migration is immediately visible to v3 code. So v4 must be additive only — no column renames, drops, or type changes — otherwise every v3 domain breaks the moment v4 migrates. This needs to be a written rule before we run two versions in production.
Part F — What we would like out of the meeting
- [ ] Access to (or ownership agreement on) the deploy agent's code
- [ ] A sanitised sample Apache vhost for one customer domain
- [ ] Confirmation that a second web + API instance can run on the same box
- [ ] Agreement on where
versionis read (DB vs request payload) - [ ] A test hostname we can point at the v4 instance
- [ ] Sign-off on the additive-only migration rule