Skip to content

feat(bricks): add-on-demand infrastructure bricks and a nested service - #19

Merged
javaAndScriptDeveloper merged 31 commits into
mainfrom
feat/bricks
Oct 12, 2026
Merged

javaAndScriptDeveloper merged 31 commits into
mainfrom
feat/bricks

Conversation

@javaAndScriptDeveloper

Copy link
Copy Markdown
Owner

Summary

  • Nests the service under services/app (Gradle :app, settings.gradle.kts discovers services/*).
  • Introduces bricks: self-contained optional infrastructure under bricks/<name>/, enabled by .bricks, wired by make add BRICK=<name> / make remove BRICK=<name> (compose include: lines, env marker blocks, Java copied with __PKG__ substitution, skill install, lockfile refresh). bricks/README.md is the contract; .claude/skills/bricks the agent entry point.
  • Extracts observability (Alloy, Grafana, Prometheus, dashboard, alerts) and backup (pg_dump → rclone) from compose profiles into bricks. Adds a new minio brick (S3 client, ObjectStorageService, health indicator, unit + Testcontainers tests). Nothing is enabled by default.
  • make prod-init fills every empty *PASSWORD=; CI gains a bricks matrix job that builds each brick on a clean checkout without gating the image.
  • Fixes a pre-existing release bug: next-version.sh could skip a MAJOR bump (git log | grep -q under pipefail).

Spec: docs/superpowers/specs/2026-10-12-lego-bricks-design.md. Plan: docs/superpowers/plans/2026-10-12-lego-bricks.md.

Notes for the reviewer

  • minio/minio is no longer pullable (Docker Hub, quay, ghcr); the brick uses cgr.dev/chainguard/minio:latest (real MinIO, ships mc; latest only on Chainguard's free tier).
  • A VPS still on the COMPOSE_PROFILES layout must follow docs/deployment.md → "Upgrading a host from the COMPOSE_PROFILES layout" before make prod-up, or backups stop silently.
  • Enabling a brick on a running host: make add on the laptop, push, then on the VPS git pull, copy the brick's .env.prod block, make prod-up (documented in docs/deployment.md).

Test plan

  • make build (unit + integration + JaCoCo) with no bricks and with each brick added
  • make test-scripts (11 suites incl. per-brick tests; all-bricks compose render; :? guards still fire)
  • add → build → remove round trip clean for each brick and all three together
  • make rename after make add BRICK=minio builds under the new package
  • zizmor clean; scripts/lint-commits.sh main..HEAD clean
  • First CI run of the bricks matrix job on GitHub (never executed on a real runner yet)

🤖 Generated with Claude Code

javaAndScriptDeveloper and others added 28 commits October 12, 2026 00:45
The template carries every optional piece (backups, Alloy, Grafana) next to the core, gated by profiles and
documented four times over, so a new project inherits all of it and an agent has to read all of it to find one
piece. This spec turns optional infrastructure into self-contained bricks under bricks/<name>/ that a single
command wires in or out, and nests the service under services/app so other things can sit beside it.

Rulings taken while the user slept are listed in section 10 of the spec.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Ten tasks: nest the service, wire .bricks into Make/Gradle/tests, write brick.sh, extract observability and
backup, generate every prod password, add the minio brick, verify every brick in CI, rewrite the docs, run the
acceptance round trips.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Gradle becomes a multi-project build that discovers services/*; the root keeps plugin versions and formatting.
Other things (bricks, later more services) can now sit beside the service instead of inside it.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
…e test runner

One file lists what is on. Make includes each enabled brick's targets, the service build applies each enabled
brick's dependencies, and script tests under bricks/*/tests run regardless so a disabled brick cannot rot.

Co-Authored-By: Claude Opus 5 <[email protected]>
Presence of a file in bricks/<name>/ decides what gets wired: compose include lines, env marker blocks, Java copied
into the service package, a skill, the lockfile. Both directions are idempotent so a repeated command is harmless.

Co-Authored-By: Claude Opus 5 <[email protected]>
… brick

Metrics collection, dashboards and alerts are optional per project. They now live in bricks/observability and are
wired by `make add BRICK=observability` instead of a compose profile and four documents.

Co-Authored-By: Claude Opus 5 <[email protected]>
The sidecar, its targets, tests and runbook now live in bricks/backup and are wired by `make add BRICK=backup`.
vps.sh reads .bricks on the host instead of COMPOSE_PROFILES to know whether to report backup freshness.

deploy-config.test.sh carries three minio assertions commented out until the minio brick lands. Its per-service
awk range ended on its own first line, so it is replaced by a small svc_block helper.

Co-Authored-By: Claude Opus 5 <[email protected]>
…p brick

The rewrite of deploy-config.test.sh dropped the check that rclone tokens never enter the docker build context, and
the sh -n/shellcheck pass on backup.sh. The context is now bricks/backup, so the guards run there again. Also make
the all-bricks scratch copy tolerate deleted files and vps.sh read .bricks independent of VPS_APP_DIR spelling.

Co-Authored-By: Claude Opus 5 <[email protected]>
A brick (minio first) can ask for a secret by leaving `X_PASSWORD=` empty in its env block; prod-init fills it on
the host, so no brick secret is ever typed or committed. Postgres is no longer the only generated password.

Co-Authored-By: Claude Opus 5 <[email protected]>
A failing openssl inside a command substitution was silently ignored, so .env.prod could be written with an empty
POSTGRES_PASSWORD. The file is now built in memory and written only after every password is generated, and a
final line without a trailing newline is no longer dropped.

Co-Authored-By: Claude Opus 5 <[email protected]>
First brick that carries Java: proves that compose, env, dependencies, main code, tests and a skill can all come
from one folder and leave no residue on removal. The AWS SDK client works unchanged against Hetzner Object Storage.
The image is Chainguard's MinIO build because MinIO Inc. no longer publishes minio/minio.

Co-Authored-By: Claude Opus 5 <[email protected]>
…rip clean

The health probe listed all buckets, which bucket-scoped credentials on managed stores forbid, leaving the app
permanently DOWN; it now heads the configured bucket. SDK calls had no timeout, so a hung store could hold a probe
past the compose interval. Missing buckets read as 404 and racing bucket creation is tolerated.

The lockfile header is regenerated so add/remove leaves no diff, and the deploy-config and init-prod-env script tests
no longer break when a brick is enabled (core render comes from a brick-free scratch copy; demo block renamed).

Co-Authored-By: Claude Opus 5 <[email protected]>
Bricks are inert until enabled, so a plain build cannot notice one that rotted; adding, building and removing each
one on its own runner catches that. The job is deliberately not a gate for image, so push to live stays as fast.

Co-Authored-By: Claude Opus 5 <[email protected]>
The root documents shrink to the core; each brick's README is its runbook and bricks/README.md is the contract for
writing one, so an agent reads one folder to work on one component.

Co-Authored-By: Claude Opus 5 <[email protected]>
… apart

A failed lockfile refresh used to leave add/remove half-applied with no hint,
so the message now names the recovery commands. Names such as '..' passed the
directory check, and a .bricks without a final newline got the name glued on.

Co-Authored-By: Claude Opus 5 <[email protected]>
grep -q exits on the first match, git log can die of SIGPIPE, and pipefail
then reported no match, skipping a major bump intermittently.

Co-Authored-By: Claude Opus 5 <[email protected]>
In a derived project the committed brick made add a no-op and remove delete
tracked files, so the clean-tree check failed on every push; the build job
already covers an enabled brick.

Co-Authored-By: Claude Opus 5 <[email protected]>
SERVICE doubled as both, so bare make prod-logs tailed only app and the help
text lied. GRADLE_SERVICE now names the Gradle project; vps.sh no longer
passes an empty service name to compose.

Co-Authored-By: Claude Opus 5 <[email protected]>
The generated record toString printed secretKey, which CLAUDE.md forbids in
logs; it is now masked and a unit test pins that.

Co-Authored-By: Claude Opus 5 <[email protected]>
The all-bricks render merges partial app services; losing the Watchtower
labels would silently stop deploys, so the test now pins them.

Co-Authored-By: Claude Opus 5 <[email protected]>
… upgrade

Enabling a brick needs git pull, an env block and make prod-up on the host
right after the push, or a Java brick rolls into a host without its service.
Also restores the Telegram secrets row, states the include: form brick.sh
manages and fixes a garbled checklist step.

Co-Authored-By: Claude Opus 5 <[email protected]>
…f copying it

Copying Java into services/app meant package rewriting, a Spotless pass on
add, and brick code that silently diverged from its source. A brick with
bricks/<name>/java/build.gradle.kts is now project 🧱<name> while
listed in .bricks, so add/remove touch no service source and the brick is
built and tested on its own.

Co-Authored-By: Claude Opus 5 <[email protected]>
…ject

The brick no longer reaches into the application: beans come from an
auto-configuration (each @ConditionalOnMissingBean so an app can override),
and a missing key is a ResponseStatusException instead of the app's
ApplicationException. Its own build file and lockfile own the AWS SDK and
test dependencies, and the integration test boots a minimal context rather
than the application's.

Co-Authored-By: Claude Opus 5 <[email protected]>
…roject

The guard for the Grafana assets belongs to the brick, not to a copy of it
in the service, and it needs only JUnit, AssertJ and Jackson.

Co-Authored-By: Claude Opus 5 <[email protected]>
Brick Java now has its own Gradle build files and lockfiles under
bricks/*/java, which Dependabot never looked at.

Co-Authored-By: Claude Opus 5 <[email protected]>
The docs still said brick Java is copied with __PKG__ substituted; authors
need the new contract (Gradle project, fixed package, auto-configuration,
no app imports) and users need to know a bean can be overridden.

Co-Authored-By: Claude Opus 5 <[email protected]>
A Boot/BOM bump refreshed only the app lockfile and broke every enabled
brick's locked resolution. The minio README still suggested removing the
brick to keep its Java, which now drops the subproject. The rename test
fixture now mirrors the real brick layout.

Co-Authored-By: Claude Opus 5 <[email protected]>
Pins that the brick registers its beans, binds app.minio.*, and backs off
for an S3Client the application defines.

Co-Authored-By: Claude Opus 5 <[email protected]>
@javaAndScriptDeveloper

Copy link
Copy Markdown
Owner Author

Update: brick Java is now a Gradle subproject, not a copy.

A brick with Java has bricks/<name>/java/ as Gradle project :bricks:<name>, included by settings.gradle.kts only while the brick is listed in .bricks. Fixed package template.bricks.<name>, registered through Spring Boot auto-configuration (AutoConfiguration.imports), every bean @ConditionalOnMissingBean so the app can override. scripts/brick.sh no longer copies or deletes any Java; make lock refreshes enabled bricks' lockfiles too; Dependabot reads /bricks/*/java.

Verified: make build with no bricks / minio+observability / all three; add→build→remove clean for each; rename-after-add builds; MinioAutoConfigurationTest covers wiring and back-off without Docker.

🤖 Generated with Claude Code

Compose < 5 (ubuntu-latest ships 2.38) rejects an included file that
redefines a service of the parent ("services.app conflicts with imported
resource"), which the minio brick did for app. Partials of core services
now live in override(.prod).yml, which the Makefile adds as -f files while
the brick is enabled; -f merging works on every Compose version. The
Makefile is the single place that builds the compose -f list (make
print-prod), and vps.sh evals that on the VPS instead of repeating it.

Co-Authored-By: Claude Opus 5 <[email protected]>
…t BOM

Trivy fails CI on fixable HIGH/CRITICAL findings in the service and brick
lockfiles. Boot 4.1.0 still manages Jackson 3.1.4 / 2.21.4; the fixes are in
3.1.7 / 2.21.7. Netty 4.2.17, httpcore5 5.4.3 and Spring Framework 7.0.9
were flagged by the same scan, so they are overridden the same way (the
bricks import the Boot BOM too). Drop each once the BOM catches up.

Co-Authored-By: Claude Opus 5 <[email protected]>
AWS SDK classes are annotated with Jackson 2 @JsonInclude, which the brick
did not see, so javac printed "unknown enum constant Include.NON_EMPTY"
on every compile. compileOnly is enough: nothing at runtime needs it.

Co-Authored-By: Claude Opus 5 <[email protected]>
@javaAndScriptDeveloper
javaAndScriptDeveloper merged commit d23c4de into main Oct 12, 2026
8 checks passed
@javaAndScriptDeveloper
javaAndScriptDeveloper deleted the feat/bricks branch October 12, 2026 13:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant