Skip to content

initiative: pivot Hyveon to a standalone Electron desktop app #214

@CoderCoco

Description

@CoderCoco

The single tracking issue for the Hyveon desktop pivot. Authoritative scope, locked decisions, and migration plan live in the design spec.

Vision

Convert Hyveon from a Nest+React web app (operator-driven, Docker-shipped, hand-run terraform apply) into a single-binary cross-platform Electron desktop application that owns the full deploy lifecycle — config editing, plan/apply/destroy, log streaming — while keeping AWS as the v1 cloud and leaving abstraction seams for GCP/Azure later.

Milestones

Each milestone is independently releasable.

M1 — Runs as Electron

After M1: desktop app launches; every current feature works inside the Electron window via IPC. Operator still hand-runs terraform apply out of band.

M2 — Manages config

After M2: operator can add/edit/remove game-server entries from inside the app; tfvars round-trip to S3 with optimistic locking.

M3 — Runs terraform

After M3: fully self-managing. Fresh install on a clean machine reaches "dashboard ready" through the wizard; plan/apply/destroy entirely from the UI.

M4 — Polish & ship

After M4: shippable. NSIS / DMG / AppImage from CI; electron-updater wired (disabled until signing).

Companion housekeeping

Execution order

Epic-level dependency graph

A (#135) ──► B (#136) ──► F (#140)
   │           │
   │           ╰──► #82 ◄──╮
   ▼                       │
G (#141)                   │  (#82 needs both B and #81)
                           │
C (#137) ──► #80 ──► #81 ──╯
   │
   ╰──► D (#138) ──► E (#139)

#213  (standalone; land after rebrand, before D ships runs table)

First three to actually pick up

  1. feat(workspaces): rename @gsd/* scope to @hyveon/* (incl. server → desktop-main) #142 — rename @gsd/*@hyveon/* (Epic A entry-point; unblocks the entire pivot)
  2. In parallel — define the four cloud-provider interfaces (Epic C entry-points):
  3. feat(desktop-main): adopt nestjs-electron-ipc-transport in bootstrap path #151 — adopt nestjs-electron-ipc-transport (Epic B entry-point, after Epic A chassis)

Within-epic sequence

#135 — Epic A: Electron shell + build pipeline

  1. feat(workspaces): rename @gsd/* scope to @hyveon/* (incl. server → desktop-main) #142 — rename @gsd/*@hyveon/* (foundational)
  2. In parallel after feat(workspaces): rename @gsd/* scope to @hyveon/* (incl. server → desktop-main) #142:
  3. feat(build): adopt electron-vite for dev + build pipeline #144 — adopt electron-vite (can run alongside)
  4. feat(build): adopt electron-builder with three-target config #145 — adopt electron-builder (after feat(build): adopt electron-vite for dev + build pipeline #144)
  5. ci: matrix build for win/mac/linux artifacts #146 — CI matrix (after feat(build): adopt electron-builder with three-target config #145)

#136 — Epic B: IPC migration of Nest controllers (starts after Epic A chassis is in)

  1. feat(desktop-main): adopt nestjs-electron-ipc-transport in bootstrap path #151 — adopt nestjs-electron-ipc-transport
  2. In parallel after feat(desktop-main): adopt nestjs-electron-ipc-transport in bootstrap path #151:
  3. feat(web): rewrite api.service.ts over window.gsd.* IPC bridge #159 — rewrite api.service.ts (after all controllers)
  4. feat(web): replace EventSource log consumer with IPC subscription in LogsPage #160 — replace EventSource log consumer (after feat(desktop-main): convert LogsController to IPC streaming channel #154)
  5. In parallel after feat(web): rewrite api.service.ts over window.gsd.* IPC bridge #159:

#137 — Epic C: Cloud provider abstraction + AWS impl (can run in parallel with Epic A/B; blocks everything in M2)

  1. In parallel:
  2. feat(workspaces): scaffold @hyveon/cloud-aws package #168 — scaffold @hyveon/cloud-aws (after interfaces)
  3. In parallel after feat(workspaces): scaffold @hyveon/cloud-aws package #168:
  4. feat(desktop-main): CloudProviderModule with DI bindings #183 — CloudProviderModule (after all impls)
  5. refactor(terraform): split terraform/ into terraform/aws/ subdirectory #185 — split terraform/ into terraform/aws/
  6. feat(terraform): top-level composer (main.tf + variables.tf) #187 — top-level composer (after refactor(terraform): split terraform/ into terraform/aws/ subdirectory #185)
  7. chore(lint): ban @aws-sdk/* imports outside @hyveon/cloud-aws and lambda packages #195 — ESLint rule banning @aws-sdk/* imports (anytime after feat(workspaces): scaffold @hyveon/cloud-aws package #168)

#80 — Remote tfvars storage (after Epic C delivers RemoteFileStore)

  1. feat(terraform): bootstrap module — versioned S3 bucket for tfvars #84 — versioned S3 bucket terraform module
  2. feat(iam): GameServerDeployAll entries for the tfvars bucket #86 — IAM entries for the bucket
  3. feat(desktop-main): bootstrap the tfvars bucket on first run #85 — bootstrap the bucket on first run
  4. feat(desktop-main): RemoteTfvarsStore service (pull/push/diff/lock) #87RemoteTfvarsStore service
  5. feat(desktop-main): wire RemoteTfvarsStore into bootstrap path #88 — wire into bootstrap path
  6. feat(desktop-main): migration path from pre-pivot local tfvars #89 — migration path from pre-pivot local tfvars
  7. docs: document S3 tfvars workflow #90 — docs

#81TfvarsService reads from RemoteFileStore (after #80)

  1. feat(desktop-main): TfvarsService — parse and cache S3-backed tfvars #91TfvarsService parse + cache
  2. feat(desktop-main): gsd.games.list IPC — declared game_servers merged with live tfstate #92gsd.games.list IPC (declared + live merge)
  3. feat(web): Games settings page (read-only) #93 — Games settings page (read-only)
  4. feat(desktop-main): drift detection — declared-vs-live mismatches #94 — drift detection
  5. test(desktop-main): TfvarsService unit + integration coverage #95 — unit + integration tests

#82 — Add/edit/remove games via desktop UI (after #81 AND #136)

  1. feat(shared): tfvars schema validation (Zod) #97 — Zod schema validation in @hyveon/shared
  2. feat(desktop-main): write game_servers to S3 tfvars with optimistic locking #96 — write game_servers to S3 with IfMatch
  3. feat(desktop-main): gsd.games.create/update/delete IPC handlers #98 — CRUD IPC handlers
  4. In parallel:
  5. feat(web): pending-changes banner #101 — pending-changes banner
  6. feat(desktop-main): audit log in DynamoDB #102 — audit log in DynamoDB

#138 — Epic D: Local terraform orchestration (after Epic C and #81)

  1. feat(desktop-main): TerraformService scaffolding + binary detection #169TerraformService scaffolding + binary detection
  2. feat(desktop-main): TerraformService.init() with backend-config args #171init()
  3. feat(desktop-main): TerraformService.plan() producing tfplan artifact #173plan()
  4. feat(desktop-main): TerraformService.apply() with stale-plan guard #175apply() with stale-plan guard (after feat(desktop-main): TerraformService.plan() producing tfplan artifact #173)
  5. feat(desktop-main): TerraformService.destroy() with explicit confirmation #177destroy() with explicit confirmation
  6. feat(desktop-main): TerraformService.output() exposing tf outputs #178output()
  7. feat(terraform): DynamoDB runs table for terraform-run records #105 — DynamoDB runs table (inherited from closed epic: web-app-driven terraform plan/apply pipeline #83)
  8. feat(desktop-main): run-record persistence to DynamoDB with S3 log offload #179 — run-record persistence (after feat(terraform): DynamoDB runs table for terraform-run records #105)
  9. In parallel:
  10. UI work in parallel (inherited from epic: web-app-driven terraform plan/apply pipeline #83):

#139 — Epic E: First-run wizard + credentials UX (after Epic D — wizard's final step calls TerraformService.init)

  1. Foundational services in parallel:
  2. feat(web): wizard step — install prerequisites #184 — wizard step install prerequisites (after feat(desktop-main): prerequisite-detection service (terraform, aws on PATH) #182)
  3. feat(web): wizard step — pick cloud (v1: AWS only) #186 — wizard step pick cloud
  4. feat(web): wizard step — pick or paste credentials #192 — wizard step pick or paste credentials (after feat(desktop-main): AwsProfileService — list and read ~/.aws/credentials #189, feat(desktop-main): safeStorage paste-flow for credentials #197)
  5. SDK bootstraps in parallel:
  6. feat(desktop-main): IAM SimulatePrincipalPolicy check against GameServerDeployAll #208 — IAM SimulatePrincipalPolicy check
  7. feat(web): wizard step — terraform init with live log #210 — wizard step terraform init (after feat(desktop-main): SDK bootstrap of S3 state bucket #200, feat(desktop-main): SDK bootstrap of DynamoDB lock table #203, feat(desktop-main): SDK bootstrap of S3 tfvars bucket #205, and feat(desktop-main): TerraformService.init() with backend-config args #171 from Epic D)
  8. feat(web): Reconfigure entry point in Settings #211 — Reconfigure entry point in Settings

#140 — Epic F: Test migration to Playwright Electron (after Epic B)

  1. feat(test): adopt _electron.launch() Playwright API in e2e config #188 — adopt _electron.launch() Playwright API
  2. feat(test): test-only window.gsd.__test.mock() injected by preload #198 — test-only window.gsd.__test.mock() injection
  3. Spec migrations in parallel (after feat(test): adopt _electron.launch() Playwright API in e2e config #188, feat(test): test-only window.gsd.__test.mock() injected by preload #198):
  4. feat(test): recast integration tier — main-process + IPC + AWS-mock #199 — recast integration tier (main-process + IPC + AWS-mock)
  5. feat(test): app/test/fake-terraform.mjs scripted-output stand-in #201fake-terraform.mjs scripted stand-in
  6. test(integration): orchestrator coverage using fake-terraform.mjs #204 — orchestrator integration coverage (after feat(test): recast integration tier — main-process + IPC + AWS-mock #199 and feat(test): app/test/fake-terraform.mjs scripted-output stand-in #201)

#141 — Epic G: Distribution + auto-update scaffolding (rides alongside M2/M3, finalizes at M4)

  1. ci: tag-triggered release workflow publishing artifacts to GitHub Releases #202 — tag-triggered release workflow (after ci: matrix build for win/mac/linux artifacts #146 — CI matrix in Epic A)
  2. feat(desktop-main): electron-updater wired with disabled feature flag #206electron-updater wired with disabled feature flag
  3. docs: unsigned-MVP install instructions per OS #207 — unsigned-MVP install docs per OS
  4. docs: code-signing roadmap (Apple Developer, Azure Trusted Signing) #209 — code-signing roadmap

#213 — terraform project_name migration (standalone follow-up)

Out of scope

  • GCP / Azure cloud provider implementations. Epic C ships the four interface seams; second-cloud impls are deliberately deferred.
  • Code signing and active auto-update. Epic G ships the scaffolding; flipping the feature flag and paying for Apple Developer / Azure Trusted Signing is a follow-up capability.
  • Multi-user desktop access. Desktop is single-operator by design; Discord-side multi-user permissions stay fully intact.
  • Closing the Docker-compose / Nest-on-port-3001 deployment story. That goes away on M1; not tracked here separately.

Acceptance

  • A non-technical operator can download a single Hyveon installer (.exe / .dmg / .AppImage) on Windows / macOS / Linux, run a first-time wizard, and have a working AWS deployment without ever touching the terraform or AWS CLIs manually.
  • The desktop app is the only operator surface — no setup.sh, no Docker, no terraform apply from a terminal.
  • Discord-side gameplay (slash commands, multi-user permissions, autocomplete) is unchanged from the user's perspective.
  • The CloudProvider abstraction in @hyveon/shared has exactly one v1 implementation (AwsCloudProvider) and no AWS types leak through the interface signatures.

Status

Track milestone state via project board #2 filtered by this issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions