Make Setup Command Design

Date: 2026-05-10
Status: Approved in brainstorming, ready for planning

1) Goal and scope

Add a root make setup command that prepares a newly created git worktree for local development, so the workflow becomes: create worktree, run make setup, then start working.

In scope:

  • Add a root make setup target.
  • Bootstrap JS/TS dependencies with Bun.
  • Create missing local env files from tracked examples.
  • Start required local infrastructure with Docker Compose.
  • Wait for infrastructure readiness before backend database work.
  • Run backend migrations.
  • Seed demo data only when the target local database is empty.
  • Update onboarding docs to make make setup the primary worktree bootstrap path.

Out of scope:

  • Installing system prerequisites such as Bun or Docker.
  • Global machine provisioning beyond this repository.
  • Mobile app onboarding.
  • Firmware onboarding.
  • Production deployment behavior.

2) User contract

make setup is a strict, fail-fast worktree bootstrap command.

Expected behavior:

  1. Validate Docker CLI availability.
  2. Validate Docker daemon availability.
  3. Run bun install at repo root.
  4. Create backend/.env from backend/.env.example only if backend/.env does not already exist.
  5. Create frontend/.env from frontend/.env.example only if frontend/.env does not already exist.
  6. Start infra via docker compose -f docker-compose.infra.yml up -d.
  7. Wait until required services are healthy enough for migrations and seed.
  8. Run bun run migrate.
  9. Run demo seed only if the local database is empty.
  10. Print the next step: make dev.

Failure policy:

  • If Docker is unavailable, exit immediately with a clear error.
  • If infra does not become ready within a bounded wait window, exit immediately with a clear error.
  • If migrations fail, do not continue to seed.
  • If the database-empty check fails, exit non-zero and surface the failing step.
  • If conditional seeding runs and fails, exit non-zero and surface the failing step.

Use a thin Makefile target that delegates orchestration to a shell script.

Update the root Makefile to:

  • add setup to .PHONY
  • add a setup: target that runs bash ./scripts/setup.sh

Reasoning:

  • The existing Makefile already acts as a command surface.
  • Setup orchestration needs branching, validation, and readiness loops that are harder to maintain inline.
  • A dedicated script keeps the Makefile readable and reduces risk of quoting mistakes.

Create scripts/setup.sh as the single owner of onboarding orchestration.

Script requirements:

  • use strict shell options: set -euo pipefail
  • emit clear stage labels for each onboarding step
  • keep logic idempotent where reasonable
  • stop on first failure

Planned structure:

  1. check_docker_cli
  2. check_docker_daemon
  3. install_dependencies
  4. ensure_env_files
  5. start_infra
  6. wait_for_infra
  7. run_migrations
  8. seed_demo_data_if_database_empty
  9. print_success_message

This can be implemented with shell functions in one file. No additional runtime or helper package is needed.

4) Environment file policy

The setup command should not overwrite an existing local env file.

Rules:

  • If backend/.env is missing, copy from backend/.env.example.
  • If frontend/.env is missing, copy from frontend/.env.example.
  • If either file already exists, leave it unchanged and print that it was preserved.

Rationale:

  • A new worktree gets sensible defaults automatically.
  • An existing worktree does not lose local customization by re-running make setup.

5) Infrastructure readiness

The script must not rely on docker compose up -d alone before running migrations.

Readiness design:

  • Start the existing infra stack from docker-compose.infra.yml.
  • Wait for the postgres service to report healthy before running migrations.
  • Optionally verify other services are started or healthy for clearer onboarding diagnostics, but only PostgreSQL readiness is a hard requirement for migration and seed.

Recommended policy:

  • Treat postgres, redis, emqx, and minio startup as part of onboarding success messaging.
  • Gate database migration/seed on PostgreSQL health.
  • Always run docker compose ... up -d, even if shared local infra is already running, because that is idempotent and keeps setup behavior simple across worktrees.

Recommended wait behavior:

  • bounded polling loop with a clear timeout
  • print current wait status while polling
  • on timeout, print a suggestion to inspect docker compose -f docker-compose.infra.yml ps

6) Seed behavior and idempotency

make setup should not seed unconditionally. It should seed only when the target local database is empty.

Design constraint:

  • Multiple worktrees may share the same local PostgreSQL instance and the same local development database.
  • Re-running setup in a second worktree must not duplicate demo data or fail simply because another worktree has already initialized the database.

Required handling:

  • Before running bun run seed:demo, detect whether the local database is empty.
  • If empty, run the demo seed.
  • If not empty, skip the seed and print that setup detected an existing initialized database.

Preferred direction:

  • Use a simple, explicit database-emptiness check rather than relying on duplicate-key failures from the seed path.
  • The emptiness check should be based on a stable application-level signal, such as presence of rows in a core table populated by the demo seed, rather than Docker/container state.

Implementation note:

  • The exact sentinel query should be chosen after inspecting the current seed path and schema.
  • If there is no reliable sentinel yet, implementation may add one, but the command contract remains: seed only when the database is empty.

7) Output and operator guidance

The command should be explicit about what it is doing.

Minimum output:

  • stage start message
  • success or failure message per major stage
  • final success summary with:
    • env files status
    • infra started
    • migrations applied
    • demo data seeded or skipped
    • next command: make dev

The script should avoid noisy low-signal output beyond what underlying tools already print.

8) Testing strategy

This change mixes shell orchestration and existing app commands, so testing should be split between targeted behavior checks and command-level verification.

8.1 TDD expectations

No production behavior change should be added without first defining the failing verification for that behavior.

Likely sequence:

  1. Define the missing worktree bootstrap behavior in documentation or verification steps.
  2. Add any focused tests needed for extracted logic if implementation introduces testable helpers in code.
  3. Run command-level verification for the shell flow.

8.2 Verification targets

Implementation should verify at least:

  • make setup exists and is discoverable in the root Makefile
  • env-file creation preserves existing files
  • Docker unavailability produces a clear failure
  • setup can complete successfully in a valid local environment
  • setup skips seed when the shared local database is already initialized
  • onboarding docs reflect the new primary worktree flow

8.3 Repo-level verification

After implementation:

  • run any targeted tests introduced for this work
  • run bun run check

If full make setup execution cannot be validated in the current environment, report that explicitly.

9) Documentation changes

Update README.md so the primary onboarding flow becomes:

make setup
make dev

This section should be framed around a new git worktree, not just a first clone.

Manual fallback details can remain below that section:

  • Bun install
  • env-file copy
  • migration command

The Make Commands table should also include make setup.

10) Risks and mitigations

Risk: setup becomes flaky because migrations run before PostgreSQL is ready.
Mitigation: explicit readiness polling on PostgreSQL health.

Risk: rerunning setup duplicates seed data or fails with uniqueness errors.
Mitigation: gate seed execution on an explicit database-empty check.

Risk: overwriting local env files breaks existing developers.
Mitigation: copy env files only when missing.

Risk: inline shell in Makefile becomes hard to maintain.
Mitigation: keep orchestration in scripts/setup.sh.

Risk: the chosen empty-database sentinel is too weak and skips seed incorrectly.
Mitigation: base the check on a stable seeded application table and verify it against the current demo seed behavior.

11) Open implementation decisions already resolved

The following decisions are fixed for implementation:

  • make setup is the primary worktree bootstrap path.
  • Docker is required and absence is a hard failure.
  • The command must always run bun install.
  • The command must always run docker compose -f docker-compose.infra.yml up -d.
  • The command should prepare demo data only when the local database is empty.
  • The Makefile should stay thin and delegate to a script.