From e41c2d912fb398c062b48a070177bf366f23d3ea Mon Sep 17 00:00:00 2001 From: Niklas Zender Date: Mon, 13 Apr 2026 10:33:09 +0200 Subject: [PATCH] feat: add famedly.standards.platform module for local dev environments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wraps famedly-platform so individual service repos only need a minimal `image.name` + optional `image.chart` to get a full k3d/Tilt dev setup. Supports Docker and Nix builds, hot-reload, Cargo patch overrides, and local Helm chart overrides — all through a single, clean Nix API. Made-with: Cursor --- README.md | 57 ++++++++++ flake.nix | 10 +- nix/modules/platform.nix | 231 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 295 insertions(+), 3 deletions(-) create mode 100644 nix/modules/platform.nix diff --git a/README.md b/README.md index 01f4ebe..8840e56 100644 --- a/README.md +++ b/README.md @@ -40,4 +40,61 @@ See **[docs/adopting.md](docs/adopting.md)** for existing repos, configuration r | `devShell` | `famedly-*` CLI (see above) | | `rules` | `.cursor/rules/…`, `CLAUDE.md` | | Workflows | `ci`, `general-checks`, `dart-ci` (multi-package), `rust-ci`, `docker` (multi-arch/simple), `review-app`, `github-pages`, `hookd-deploy`, `release`, `publish-crate`, `publish-pub`, `docker-backend`, `docker-bake`, `ansible-ci`, `ai-review`, `fast-forward`, `add-to-project`, `update-engineering-standards` | +| `platform` | local k3d + Tilt dev environment (wraps `famedly-platform`) | | `projects` | monorepo: per-folder lint/hooks/dependabot | + +--- + +## Platform dev environment + +Enable a local Famedly platform (k3d + Tilt) that builds your service image on every code change while all other services run from pre-built registry images. + +```nix +perSystem = { ... }: { + famedly.standards.platform = { + enable = true; + image = { + name = "famedly-operator"; # must match the Helm subchart name + chart = ./helm/famedly-operator; + }; + }; +}; +``` + +Then start with `nix run .#famedly-platform-up` and tear down with `nix run .#famedly-platform-down`. + +### All platform options + +| Option | Default | Description | +|--------|---------|-------------| +| `image.name` | `null` | Helm subchart name — Tilt builds this image locally | +| `image.src` | `"."` | Docker build context (relative to repo root) | +| `image.build.docker` | `null` | Dockerfile path (default: `/Dockerfile`) | +| `image.build.nix` | `null` | Nix flake package for container image (e.g. `"my-container"`) | +| `image.chart` | `null` | Local Helm chart dir — overrides the published subchart | +| `image.hotReload` | `[]` | Push file changes into the container without rebuild | +| `image.patch.cargo` | `{}` | Cargo `[patch]` overrides for local Rust dependency development | +| `extraImages` | `[]` | Additional local images (monorepo setups) | +| `chart` | pinned `helm-charts` | Platform umbrella chart source | +| `values` | `{}` | Additional Helm values | +| `ports` | 9310, 8080, 8008, 8282 | k3d port mappings | +| `testCommand` | — | CI mode: run after environment is ready | +| `extraManifests` | `[]` | Extra K8s manifests to apply before the chart | + +### Hot-reload (frontends) + +```nix +image.hotReload = [ + { from = "build/web"; to = "/usr/share/nginx/html"; } +]; +``` + +### Cargo patch (Rust dependency override) + +```nix +image.patch.cargo = { + "https://github.com/famedly/zitadel-rust-client" = "../zitadel-rust-client"; +}; +``` + +See [`famedly-platform` README](https://github.com/famedly/famedly-platform) for full platform documentation. diff --git a/flake.nix b/flake.nix index 89078e4..9bd4d89 100644 --- a/flake.nix +++ b/flake.nix @@ -25,6 +25,10 @@ url = "github:famedly/helm-charts"; flake = false; }; + famedly-platform = { + url = "github:famedly/famedly-platform"; + inputs.flake-parts.follows = "flake-parts"; + }; }; outputs = @@ -42,6 +46,7 @@ standards = ./nix/modules; workflows = importApply ./nix/modules/workflows args; preCommitHooks = importApply ./nix/modules/pre-commit-hooks.nix args; + platform = importApply ./nix/modules/platform.nix args; }; in { @@ -141,10 +146,9 @@ enable = true; fossHooks.enable = false; }; - e2e = { + platform = { enable = true; - chart = "${inputs.helm-charts}/e2e-platform"; - portForwards = [ + ports = [ "9310:9310@loadbalancer" "8080:8080@loadbalancer" "8282:8282@loadbalancer" diff --git a/nix/modules/platform.nix b/nix/modules/platform.nix new file mode 100644 index 0000000..1a38e87 --- /dev/null +++ b/nix/modules/platform.nix @@ -0,0 +1,231 @@ +# Local platform dev environment module. +# +# Wraps famedly-platform for single-repo development: one `enable` flag +# and a concise `image` description is all a consumer needs. +# +# perSystem = { ... }: { +# famedly.standards.platform = { +# enable = true; +# image = { +# name = "famedly-operator"; +# chart = ./helm/famedly-operator; +# }; +# }; +# }; +# +# Then run `famedly-platform-up` to start k3d + Tilt with the local +# image build. All other platform services use pre-built registry images. + +{ + inputs, + flake-parts-lib, + ... +}: +_caller-args: +{ + imports = [ inputs.famedly-platform.flakeModules.default ]; + + options.perSystem = flake-parts-lib.mkPerSystemOption ( + { + config, + pkgs, + lib, + ... + }: + let + cfg = config.famedly.standards.platform; + img = cfg.image; + hasImage = img.name != null; + + localImages = + (lib.optional hasImage ( + { + subchart = img.name; + context = img.src; + } + // lib.optionalAttrs (img.build.docker != null) { + dockerfile = img.build.docker; + } + // lib.optionalAttrs (img.build.nix != null) { + nix_target = img.build.nix; + } + // lib.optionalAttrs (img.hotReload != [ ]) { + live_update = map (r: { + sync = { + local = r.from; + container = r.to; + }; + }) img.hotReload; + } + // lib.optionalAttrs (img.patch.cargo != { }) { + cargo_patch = img.patch.cargo; + } + )) + ++ cfg.extraImages; + + localChartOverrides = lib.optional (hasImage && img.chart != null) img.chart; + in + { + options.famedly.standards.platform = { + enable = lib.mkEnableOption "Local platform dev environment (k3d + Tilt)"; + + image = { + name = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = '' + Service name (must match the Helm subchart name in the + platform umbrella chart). When set, Tilt builds this + repo's container image locally on every source change. + ''; + example = "famedly-operator"; + }; + + src = lib.mkOption { + type = lib.types.str; + default = "."; + description = "Docker build context directory, relative to the repo root."; + }; + + build = { + docker = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = '' + Path to the Dockerfile (relative to the repo root). + When null, defaults to /Dockerfile. + Mutually exclusive with build.nix. + ''; + }; + + nix = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = '' + Nix flake package that produces a container image + (e.g. "famedly-control-service-container"). + When set, Tilt uses `nix build` instead of `docker build`. + Mutually exclusive with build.docker. + ''; + example = "famedly-control-service-container"; + }; + }; + + chart = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + description = '' + Local Helm chart directory. Overrides the published + subchart in the umbrella chart — use this to test chart + changes alongside code changes. + ''; + example = "./helm/famedly-operator"; + }; + + hotReload = lib.mkOption { + type = lib.types.listOf ( + lib.types.submodule { + options = { + from = lib.mkOption { + type = lib.types.str; + description = "Local path (relative to src) to watch and push."; + }; + to = lib.mkOption { + type = lib.types.str; + description = "Container path to receive the files."; + }; + }; + } + ); + default = [ ]; + description = '' + Hot-reload rules: push changed files directly into the + running container without rebuilding the image. Useful + for frontend builds (HTML/JS/CSS → nginx). + ''; + example = [ + { + from = "build/web"; + to = "/usr/share/nginx/html"; + } + ]; + }; + + patch = { + cargo = lib.mkOption { + type = lib.types.attrs; + default = { }; + description = '' + Cargo [patch] overrides — maps git URLs to local paths + for developing against a local checkout of a Rust dependency. + ''; + example = { + "https://github.com/famedly/zitadel-rust-client" = "../zitadel-rust-client"; + }; + }; + }; + }; + + extraImages = lib.mkOption { + type = lib.types.listOf lib.types.attrs; + default = [ ]; + description = '' + Additional local images for monorepo setups. + Uses the same schema as famedly.platform.localImages. + ''; + }; + + testCommand = lib.mkOption { + type = lib.types.str; + default = "echo 'No test command configured'"; + description = "Command executed by `famedly-platform` (CI mode) after the environment is ready."; + }; + + chart = lib.mkOption { + type = lib.types.either lib.types.str (lib.types.attrsOf lib.types.str); + default = "${inputs.helm-charts}/e2e-platform"; + description = '' + Platform umbrella chart source. Defaults to the pinned + helm-charts input from engineering-standards. + ''; + }; + + values = lib.mkOption { + type = lib.types.attrs; + default = { }; + description = "Additional Helm values for the platform chart."; + }; + + ports = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ + "9310:9310@loadbalancer" + "8080:8080@loadbalancer" + "8008:8008@loadbalancer" + "8282:8282@loadbalancer" + ]; + description = "k3d cluster port mappings (format: host:container@loadbalancer)."; + }; + + extraManifests = lib.mkOption { + type = lib.types.listOf lib.types.path; + default = [ ]; + description = "Additional Kubernetes manifests to apply before the platform chart."; + }; + }; + + config = lib.mkIf cfg.enable { + famedly.platform = { + enable = true; + chart = cfg.chart; + values = cfg.values; + ports = cfg.ports; + testCommand = cfg.testCommand; + extraManifests = cfg.extraManifests; + localImages = localImages; + localChartOverrides = localChartOverrides; + }; + }; + } + ); +}