Skip to content

Roadmap

Synced read-only from /home/synvirt/synvirt-platform/docs-internal/roadmap.md. Edit at the source, not here.

MVP goal: a visitor can understand SynVirt, see the product, download it, find documentation and get support without leaving the ecosystem — on one server, but architected to split across hosts without touching app code.

Attack order: Phase 0 → M0 → M1 → M2 → M3 → M4 → M5 → M6 → M7 → M8.

Reproducible dev environment on Ubuntu Server (24.04 dev / 26.04 prod).

  • devbox-bootstrap.sh (idempotent)
  • compose.yaml + dev/prod overrides
  • Multi-database Postgres init (per-service role + owner)
  • Caddy tls internal for *.synvirt.test
  • SOPS/age secret layout, Taskfile, monorepo skeleton
  • Acceptance verified on the box 2026-06-28 (bootstrap, task up, CA trusted, 4 DBs present, sqlx migrate run)

Monorepo, design tokens, base UI (shadcn), i18n (en/es), dark/light, Storybook, Next.js 16 app scaffolded.

  • Next.js 16 (App Router, Turbopack) scaffolded in apps/web; / (en) and /es prerender statically and serve through Caddy at synvirt.test
  • Design tokens in @synvirt/ui/tokens.css (raw palette + shadcn-style semantic aliases), Tailwind v4 CSS-first theme in @synvirt/ui/theme.css (replaces the old packages/config Tailwind v3 preset)
  • Base UI in @synvirt/ui: Button, Card, Badge (shadcn pattern: cva + radix Slot + cn); components.json ready for shadcn add
  • i18n en/es via next-intl (localePrefix: as-needed, src/proxy.ts)
  • Dark/light via next-themes (data-theme attribute, default dark)
  • Storybook 10 (react-vite) in packages/ui with dark/light toolbar (task storybook)

Keycloak realm (synvirt-web, synvirt-portal-api, synvirt-discourse), OIDC login in Next.js, MFA + passkeys/WebAuthn, Google + Microsoft providers. Risk: test Keycloak↔Discourse SSO early.

Payload CMS embedded in web. Collections: Pages, BlogPosts, Releases, FAQs, CaseStudies, Banners, SecurityAdvisories (stub). Homepage + Product + Architecture + Pricing (preliminary) + Blog. ES/EN, dark/light, green Lighthouse.

2026-07-15 — CMS core pulled forward (the owner needs the content admin before M1): Payload 3.86 embedded in apps/web with local email/password auth — Keycloak SSO still lands in M1. en/es localization mirroring next-intl, users/media collections (media on local disk per ADR-0003), home global driving the landing with 60 s ISR, initial migration in apps/web/src/migrations/ and a seed from the static messages (pnpm payload run src/seed.ts). Admin at https://synvirt.mx/admin (SynVirt-branded: wordmark + brand-blue primary actions). Same day: cdn.synvirt.mx — Caddy serves apps/web/media directly (read-only bind mount, 7-day cache) and a Payload afterRead hook rewrites media URLs to CDN_URL; smoke test in apps/web/src/scripts/cdn-smoke.ts. Remaining M2 scope (full collections, marketing pages, blog) unchanged.

2026-07-29 — marketing site migrated to WordPress (owner’s call: the site is to be client-editable and modular). synvirt.mx now serves WordPress 7.0.2 / PHP 8.4 (apps/wordpress) with a bespoke block theme carrying the same design tokens; the marketing sections became 15 server-rendered Gutenberg blocks, so pages are composed in the editor rather than coded. Content (en/es) was migrated out of the Payload home global and the next-intl catalogues into apps/wordpress/scripts/content.json and seeded by an idempotent provision.sh. Polylang keeps the URL shape (/ en, /es/ es); topics moved to /platform/<topic>/.

Scope of the swap: only the apex. portal.synvirt.mx, docs.synvirt.mx, api.synvirt.mx, auth.synvirt.mx and synvirt.mx/dev are untouched and still run on the Next.js app / Rust API — synvirt-web.service keeps running. Payload is no longer reachable at synvirt.mx/admin; WordPress admin is at /wp-admin.

cdn.synvirt.mx was already built (see above) but had never served a byte — the media collection was empty and every image was in Next’s public/. It now serves WordPress uploads: the 12 product/brand images were imported into the media library, the vhost points at apps/wordpress/uploads, and mu-plugins/synvirt-cdn.php rewrites attachment URLs, srcsets and post content. Verified: 8/8 homepage images load from the CDN, 0 local.

2026-07-29 (later) — comparison page. /compare/ + /es/comparar/: a 10-row table against four named platforms (vSphere, Nutanix AHV, Proxmox VE, Morpheus), the six migrator adapters framed as “leaving what you have today”, the five host roles, and a substantiation notice. SynVirt claims are sourced from the product CLAUDE.md on 10.10.26.1 and from crates/migrator/src (6 live-pull adapters, 10 offline formats — the site had been saying nine). The homepage table was rebuilt to the same four competitors and carries the same notice.

Note: the product repo forbids naming those vendors inside that repository. That rule governs product code and docs; the marketing site names them by the owner’s decision. Comparison copy must not be copied back into the product repo.

Fixed while doing it: the header and footer navigation had never rendered since the theme was built. Polylang answers theme_mod_nav_menu_locations from its own polylang[nav_menus] option, which the seed was not writing — so has_nav_menu() returned true under wp-cli (no language context) and false on the front end, and the menus silently disappeared.

Infra note: the box’s LV only used half its disk (18.5 of 37 GB) and hit 100 % during the install; extended to the full VG (lvextend -l +100%FREE).

Axum + SQLx + OpenAPI (utoipa) → generates packages/api-client. Entities: Organizations, Users (Keycloak sub mirror), Subscriptions, Licenses, Downloads, SupportCases, AuditLog. JWT auth via Keycloak JWKS.

Dashboard + 9 MVP modules (org, subscriptions, licenses, downloads, support, security, notifications). Soft warnings over hard restrictions.

S3-compatible storage returns here (pgsty/minio fork, Garage or cloud S3/R2 — see ADR-0003 amendment); signed URLs issued by portal-api after entitlement check. Per-artifact SHA-256, signature, size, build ID, channel, arch. Never serve binaries from the web server’s disk. Move Payload media from local disk to the same backend (@payloadcms/storage-s3).

Stripe Checkout + Billing; webhooks → entitlement + Odoo push (idempotent). SPEI/transfer + quote + PO manual flow. Pricing page wired to Stripe products.

Fumadocs (versioned: 1.0/, latest/, release-notes/). Discourse self-hosted (official discourse_docker, NOT in base compose) + DiscourseConnect SSO. Structured bug-report form → support case. status.synvirt.mx hosted separately.

M8 — Search · Analytics · SEO · Hardening · Launch

Section titled “M8 — Search · Analytics · SEO · Hardening · Launch”

Meilisearch universal search + command palette. Privacy-friendly analytics (Plausible/Umami). SEO. Network segmentation verified, SOPS secrets, rate limit, WAF/Turnstile, verified backups, runbooks. Deploy on 10.10.26.4 via Cloudflare Tunnel → Caddy. Launch checklist.

  • Phase 2: orgs, advanced license admin, offline activation, full support cases, HCL, Knowledge Base, security advisories, public roadmap, installs.
  • Phase 3: partner portal, Academy/certifications, marketplace, Enterprise trials, opt-in telemetry, Trust Center, Lifecycle Matrix, Security Center.