Skip to content

Latest commit

Β 

History

156 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Blacksmith

Blacksmith is a proof of concept for an interactive isometric cloud architecture editor built with Godot 4 and godot_dart, backed by an ASP.NET Core 8 API and PostgreSQL.

Development is Google Cloud–first: Google Cloud Storage bucket creation is implemented and has been manually tested against a real GCP project. The architecture is designed to remain extensible, but AWS and Azure are not implemented.

Project documentation:


🎯 Project Context & Objectives

The main goals of this project are:

  • Build an interactive isometric grid where resources can be placed dynamically.
  • Expose a modular cloud backend API with separated domain, application, and infrastructure responsibilities.
  • Model designs, resources, and generic resource types in PostgreSQL.
  • Execute real cloud operations (currently: Google Cloud Storage bucket creation).
  • Validate a portable development workflow using devcontainers.
  • Preserve the option to support other cloud providers in the future without prematurely implementing them.

πŸŽ₯ Preview (sample)

2025-11-29.mp4

🧱 Global Architecture Overview

image

Conceptual overview: the illustration includes future components as well as current ones. In the current code, the cloud contract belongs to CloudServices.Core and its implementation to Providers.GoogleCloud, outside Blacksmith.Application. The resource type catalog is read-only; provider/metadata fields and the combined provisioning workflow shown in the image are future concepts. See the source-based ASCII diagrams for the implemented structure.

Current implementation

Component Implementation
Frontend Godot 4 + Dart (godot_dart), default Isometric3DGrid, shared IsomapGridBloc; legacy 2D AppRoot/IsoMapGrid/MenuOverlay
HTTP API ASP.NET Core 8 (CloudServices.Api)
Application Blacksmith.Application services, DTOs, and repository interfaces
Domain Blacksmith.Domain: DesignEntity, ResourceEntity, ResourceTypeEntity
Infrastructure Blacksmith.Infrastructure: EF Core, Npgsql, repositories, migrations
Cloud integration CloudServices.Core interface + Providers.GoogleCloud adapter
Database PostgreSQL 16; pgAdmin 4 for development
Cloud provider Google Cloud Storage (GCP only at present)

The backend uses a layered architecture with ports-and-adapters principles. The persistence use cases go through application services and repository ports; the original Cloud Storage endpoint uses its existing ICloudStorageService contract and Google Cloud adapter. This is not yet a fully unified hexagonal cloud-provisioning workflow.

Key API endpoints

Endpoint Purpose
POST /designs, GET /designs Create and list designs
GET /designs/{id}, PUT /designs/{id}, DELETE /designs/{id} Manage a design; deletion atomically removes undeployed resources; deployment history blocks deletion
GET /designs/{id}/resources List resources belonging to one design
POST /resources, GET /resources Persist resources and list all resources
GET /resources/{id}, PUT /resources/{id}, DELETE /resources/{id} Read, update, or delete a stored resource
GET /resource-types, GET /resource-types/{id} Read the generic resource type catalog
GET /cloud_services/ping Check the cloud controller endpoint
POST /cloud_services/create-bucket Retired (410); use authorized Save & Deploy
GET /db/ping Database health endpoint

Domain and persistence

Existing Cloud Storage Resources also support a dedicated Save-only configuration API: GET / PUT /resources/{resourceId}/configuration. It validates and stores a v1 JSON document without changing metadata CRUD contracts or provisioning GCP resources. See the configuration API contract for fields, canonical defaults, limits and migration prerequisites. The 3D editor now provides a Cloud Storage JSON configuration dialog and restores saved configurations.

Explicit local Save & Deploy is available for one Cloud Storage resource after target review, consent and backend authorization. Ordinary Save and Open never provision Google resources. See the deployment API and recovery contract for default-off configuration, migration prerequisites, ownership checks and retry limitations.

The backend also supports explicit deletion of a Resource and its empty GCP bucket, with dedicated consent, ownership verification and durable recovery. This is a separate API workflow; ordinary Resource/Design DELETE remains local-only. No deletion UI is added yet.

The backend also provides atomic configured Resource persistence: client UUIDs, conditional writes, per-resource revisions, unique Design/tile occupancy, and opt-in complete configuration read-back. The frontend uses these aggregate reads to restore multiple Cloud Storage placements, including saved configurations.

  • A Design contains zero or more Resources.
  • Every persisted Resource belongs to exactly one Design.
  • Deleting a Design atomically removes its resources and configurations if no deployment history exists. Any deployment history blocks deletion (409); cloud resources are never deleted by this action.
  • Each resource may reference a ResourceType. This reference is currently optional to preserve unknown historical resource types.
  • The initial generic resource catalog contains virtual-machine, storage, database, network, gateway, and load-balancer.
  • Existing resource type strings remain supported for backward compatibility.
  • EF Core database migrations are applied deliberately; the API does not automatically apply them at startup.

Integration boundary: With an active Design, select an empty tile and choose Cloud Storage from either palette to open its JSON configuration dialog. Save configuration atomically persists metadata and configuration through the Resource API/PostgreSQL; its cube appears only after canonical read-back succeeds. Selecting a Cloud Storage tile and choosing Cloud Storage again edits that same Resource ID/revision. Open restores multiple placements and their configurations after a complete application restart. These operations do not provision GCP resources. The other three GCP representations keep their local placement behavior. Replacing a persisted kind or deleting it through Save remains unsupported.


Local 3D editor

See the frontend script guide for dev.sh build, host-build, run, test, check and preview, including the host/container Dart configuration limitation.

See local startup and the five-cycle non-cloud smoke test for host/container terminal contexts, port 5100 diagnostics, safe dependency refresh, deployment-state semantics and isolated regression commands.

On the Ubuntu host, open the Godot editor without a deployment token:

cd frontend
bash scripts/run_local.sh
# Optional Godot arguments are forwarded unchanged.
bash scripts/run_local.sh --verbose

Run this on the host, outside the backend Dev Container. The launcher requires no backend/.env file or deployment token. It sets the project directory and preserves existing LD_LIBRARY_PATH entries. The same script can be invoked by its path from any working directory. It performs no build or deployment; prepare the existing Dart bindings and native libraries separately as described below.

Normal Godot project startup opens scenes/isometric_3d.tscn with a centered No design open welcome message. The full-window background and toolbar remain visible; tiles, resource visuals and Speed Dial are hidden and tile input is blocked. Use New Design or Open to activate a Design through the existing API flow. Creation/loading failures and cancellation keep the welcome screen; no blank Design is invented. Grid visibility follows DesignState.active, independently of its persistence ID or resource-save status. Tiles are created lazily on first activation; an active Design keeps the established camera framing.

With an active Design, the existing 3D scene supports selecting a tile and placing or replacing Cloud Storage, Compute Engine, Cloud VPN and Cloud Load Balancing cubes through a native palette. The 3D viewport and background fill the entire window. A compact, translucent Design toolbar overlays the top edge with actions on the left and dynamic status on the right; long status text elides and remains available in a tooltip. A compact + Speed Dial floats at the bottom-left. Open it to show Resources and Inspect vertically above Γ—. Resources opens a centered modal; select a tile first, choose a resource row, then confirm with Place Resource. Row selection alone never places anything. Inspect shows current tile/resource details and the existing Cloud Storage configuration entry point. Inspect remains a floating panel. Resources dims and blocks the entire editor behind its dialog, with a scrollable catalog, selected-tile context, Cancel and Place Resource. Cancel, Γ— and Escape preserve selection; clicking its backdrop keeps it open and never reaches the grid. Neither UI changes viewport dimensions. The dial collapses when an action or modal is opened. Transparent overlay areas keep normal grid input. The catalog retains full service labels and tooltips. The workspace and New Design dialog share one dark UI theme. Configuration/deployment dialogs retain their existing modal behavior. Once a Design is active, other resource kinds stay in IsomapGridBloc memory and placement makes no backend request. Cloud Storage configuration requires an active Design, the API and PostgreSQL; GCP credentials are not needed. Cancel and rejected saves preserve the original grid and JSON draft. New templates use clearly labelled example identifiers; legacy unconfigured resources open with missing required identifiers left blank.

The New Design action creates a persistent Design through the existing POST /designs API and displays its backend ID/name for the current session. This action requires the API and PostgreSQL. Set BLACKSMITH_BACKEND_URL before launching Godot to override http://localhost:5100; no GCP credentials are needed. Unsaved placements require explicit consent before they are cleared on successful creation. Cancellation and failures preserve them. The header Save skips confirmed configured resources and retains transitional metadata Save for older unconfigured local snapshots. A lost configured-write response exposes Check Save, which reconciles the original Resource UUID without another write. Closing the form keeps this session guard; no durable journal is provided for a crash during an unknown write. Use Open to browse the Designs returned by the backend, search by name, select a row and restore its saved cubes. The browser shows available update dates, sorts recent Designs first, and renders 50 rows per page. Copy ID remains available for diagnostics; normal opening requires no identifier input. Unsaved local changes require confirmation, and the current workspace is replaced only after a successful canonical load. Loading, empty results and errors stay inside the browser with Cancel, New Design or Retry as appropriate. See the Dart workflow for the isolated persistence test.

Prepare the Dart sources and generated bindings once, with the existing compatible Godot/Dart native libraries installed in frontend/. These commands explicitly use this VM's Dart SDK at /usr/lib/dart/bin/dart:

cd frontend/src
export PUB_CACHE="${PUB_CACHE:-$PWD/../.pub-cache}"
/usr/lib/dart/bin/dart pub get
/usr/lib/dart/bin/dart run build_runner build --delete-conflicting-outputs
cd ../..

# First command-line run after checkout: import native Godot resources once.
# Opening the project in the Godot editor also performs this import.
godot --path frontend --editor --import

# Normal application: actual project settings and production Dart bootstrap.
godot --path frontend

# Legacy 2D fallback, explicitly launched in its own application process.
godot --path frontend res://main.tscn

# Production-bootstrap checks, clean-launch captures and optional Linux tracing.
python3 frontend/scripts/tests/test-godot.py --application --trace-network --timeout 600 --output /tmp/blacksmith-startup
frontend/scripts/dev.sh test --empty-workspace --trace-network --timeout 600 --output /tmp/blacksmith-empty

# Existing detailed 3D and explicit legacy 2D regression fixtures.
python3 frontend/scripts/preview/run-isometric-3d.py --check --timeout 600 --output /tmp/blacksmith-placement
python3 frontend/scripts/tests/test-godot.py --timeout 600

Use Godot's Run Project (F5) for the default 3D editor. main.tscn and its two-choice legacy palette remain available; no production 2D/3D switch is added. The regression tools use temporary projects; normal application startup needs neither a fixture nor test environment variables. --trace-network requires the optional Linux strace tool. See the Dart workflow for source-generation prerequisites.

The 3D window follows actual window dimensions, with a minimum size of 960 Γ— 640, with at least 480 Γ— 320 available for the map. The render viewport fills the body inside the existing outer margins. Floating controls and popups reserve no layout width or height; camera framing uses the actual render viewport and includes maximum cube height even on an empty board, so placement does not change the zoom. Full application restart clears in-memory placements and active Design selection; use Open to load a previously saved Design. See the Issue #44 report for contracts, safeguards and the real PostgreSQL / close-and-reopen validation.


🐳 Devcontainers

Blacksmith provides frontend and backend VS Code Dev Container configurations. The graphical Godot editor/player may run on the host; using a frontend devcontainer does not by itself mean the GUI runs inside Docker.

Blacksmith development environments

Development workflow overview: the existing frontend installer prepares Dart and the extension for Godot on the host. Running the graphical editor inside a container is not established by the current setup. The backend image supplies .NET; Google Cloud CLI is installed afterward by the setup script.

Frontend Devcontainer

  • Dart SDK and Godot/Dart integration tooling.
  • Project setup through .devcontainer/devcontainer.json and scripts/install.sh.
  • Godot Dart extension artifact setup and optional dart pub get / build_runner workflow.
  • Portable development approach for Linux / Windows / WSL / VM, subject to host and GUI requirements.

Tip: Keep dart pub get, code generation (build_runner), and the Godot/Dart runtime artifacts aligned with the environment used to build and run the frontend.

Backend Devcontainer

  • .NET 8 SDK.
  • Google Cloud CLI.
  • Environment configuration through ASPNETCORE_URLS, GOOGLE_APPLICATION_CREDENTIALS, and ConnectionStrings__BlacksmithDb.
  • Dependency installation through postCreateCommand and scripts/install_dependencies.sh.
  • Bind-mounted repository and access to the Compose network backend_default.
  • GCP service-account credentials mounted read-only from outside the Git repository; no private key belongs in the repository.

The current backend configuration declares this bind mount unconditionally. Before creating or rebuilding the container, place a valid credential file at ~/.config/blacksmith/gcp-service-account.json on the host. Protect the directory with mode 0700 and the file with mode 0600. Inside the container, GOOGLE_APPLICATION_CREDENTIALS points to /run/secrets/blacksmith/gcp-service-account.json. A missing host file can prevent the configured container from starting.

After setup, check dotnet --version and gcloud --version; the initial rebuild did not automatically finish the CLI installation during the smoke test. The existing dependency script may require interactive package-installation confirmation.

Docker Compose services

The backend's docker-compose.yml provides:

Service Image Host port
PostgreSQL postgres:16 5432
pgAdmin dpage/pgadmin4:8 8081

From backend/ on the development host:

docker compose config --services
docker compose up -d
docker compose ps

To validate the backend from its devcontainer terminal:

cd /workspaces/backend
dotnet build BlacksmithBackend.sln --configuration Release
dotnet test BlacksmithBackend.sln --configuration Release --no-build

Testing note: Some PostgreSQL-specific integration tests are skipped unless their isolated PostgreSQL test environment is configured. The project also includes backend/scripts/test-postgresql.sh for those tests. Do not run destructive migration tests against the development database.

Running the API locally

Inside the backend devcontainer:

cd /workspaces/backend
dotnet run --project src/CloudServices.Api --urls http://0.0.0.0:5100

From another terminal in that container:

curl -i http://localhost:5100/cloud_services/ping

Port alignment: The retained explicit cloud client defaults to http://localhost:5100, matching the documented manual devcontainer smoke test. Set the API bind address and client URL consistently before explicit frontend-to-backend tests. Placement makes no backend request after a Design is active; the previous bucket smoke test did not validate Godot connectivity.


☁️ Google Cloud Storage smoke test

The historical bucket-creation API has been manually verified using the current backend and a real GCP service account.

POST /cloud_services/create-bucket?projectId=<PROJECT_ID>&bucketName=<BUCKET_NAME>

The user obtained a successful HTTP 200 response for a real bucket creation request through the existing HTTP β†’ controller β†’ Google Cloud SDK β†’ Cloud Storage path. Independent checks also confirmed a successful backend build, API startup, authentication, and the required project permissions.

The user deleted the temporary bucket before independent bucket-metadata verification. The later GCS metadata request returned 404, consistent with that deletion, so the bucket's historical location and project ownership were not independently confirmed from its metadata. See the smoke test report for the evidence and its limits.

It does not verify deploying a whole architecture design or automatically synchronizing PostgreSQL resources with cloud resources.

Caution: Bucket creation changes real cloud resources and may incur charges. The current API omits location and storage class; Google Cloud's defaults are US and STANDARD. Those are expected defaults, not verified metadata for the deleted test bucket. Confirm the target project, name, location and potential cost before creation, and clean up temporary resources deliberately after verification.


🧭 Roadmap and current limitations

  • Add a separately approved, explicit Save & Deploy workflow with tracked cloud outcomes.
  • Extend persistence beyond Cloud Storage and support deliberate resource removal/replacement.
  • Keep any future deployment action explicit and separate from local architecture editing.
  • Add explicit provisioning state, error handling, and recovery before combining cloud operations with database persistence.
  • Introduce ResourceConnection later; its GitHub issue exists, but implementation is deferred.
  • Continue with GCP only; retain modular boundaries for future providers rather than implementing placeholder AWS/Azure adapters.

For more detail, consult the backend architecture documentation and the whole-project ASCII diagrams.

Releases

Packages

Contributors

Languages