From 6789d26b0c9569013bfef97d761023356c8dd755 Mon Sep 17 00:00:00 2001 From: vibe_erp Date: Tue, 7 Apr 2026 15:29:48 +0800 Subject: [PATCH] chore: Dockerfile, docker-compose, GitHub Actions, Makefile, dev volume scaffolding --- .github/workflows/build.yaml | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ .github/workflows/check-architecture.yaml | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Dockerfile | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Makefile | 37 +++++++++++++++++++++++++++++++++++++ docker-compose.yaml | 42 ++++++++++++++++++++++++++++++++++++++++++ local-vibeerp/.gitkeep | 1 + local-vibeerp/config/.gitkeep | 0 local-vibeerp/config/vibe-erp.yaml | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ local-vibeerp/files/.gitkeep | 0 local-vibeerp/i18n-overrides/.gitkeep | 0 local-vibeerp/plugins/.gitkeep | 0 11 files changed, 325 insertions(+), 0 deletions(-) create mode 100644 .github/workflows/build.yaml create mode 100644 .github/workflows/check-architecture.yaml create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 docker-compose.yaml create mode 100644 local-vibeerp/.gitkeep create mode 100644 local-vibeerp/config/.gitkeep create mode 100644 local-vibeerp/config/vibe-erp.yaml create mode 100644 local-vibeerp/files/.gitkeep create mode 100644 local-vibeerp/i18n-overrides/.gitkeep create mode 100644 local-vibeerp/plugins/.gitkeep diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..2f920f6 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,70 @@ +# vibe_erp — main CI build. +# +# Runs the Gradle build and tests on every PR and on pushes to main. +# A separate job builds (but does not push) the Docker image on main. + +name: build + +on: + push: + branches: [main] + pull_request: + +jobs: + gradle: + name: gradle build + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: "21" + + - name: Cache Gradle + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', 'gradle/libs.versions.toml') }} + restore-keys: | + gradle-${{ runner.os }}- + + - name: Build + run: ./gradlew build --no-daemon + + - name: api-v1 tests + run: ./gradlew :api:api-v1:test --no-daemon + + - name: Upload test reports on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: test-reports + path: | + **/build/reports/tests/ + **/build/test-results/ + + docker: + name: docker image + runs-on: ubuntu-latest + needs: gradle + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build image (no push) + uses: docker/build-push-action@v6 + with: + context: . + file: Dockerfile + push: false + tags: vibeerp/vibe-erp:ci diff --git a/.github/workflows/check-architecture.yaml b/.github/workflows/check-architecture.yaml new file mode 100644 index 0000000..6f5ee15 --- /dev/null +++ b/.github/workflows/check-architecture.yaml @@ -0,0 +1,70 @@ +# vibe_erp — architectural guardrail CI. +# +# Defends two rules from CLAUDE.md guardrails #9 and #10: +# - PBCs may NEVER import other PBCs +# - api-v1 and reference-customer code may only see api.v1.* — never +# org.vibeerp.platform.* or org.vibeerp.pbc.* +# +# Primary defense: the root build.gradle.kts afterEvaluate hooks reject +# illegal Gradle dependencies. This workflow simply runs the build so +# violations fail CI. +# +# Secondary defense: a source-tree grep for forbidden imports inside the +# api-v1 and reference-customer trees. This catches anyone reaching past +# the build system (e.g. via reflection-friendly imports). + +name: check-architecture + +on: + pull_request: + +jobs: + build-guard: + name: gradle dependency rule + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: "21" + + - name: Cache Gradle + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', 'gradle/libs.versions.toml') }} + + - name: Build (enforces dependency rule via afterEvaluate hooks) + run: ./gradlew build --no-daemon + + import-guard: + name: forbidden-import grep + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Reject internal imports from api-v1 and reference-customer + shell: bash + run: | + set -euo pipefail + violations=0 + for path in api/api-v1/src reference-customer; do + if [ -d "$path" ]; then + if grep -RInE 'import[[:space:]]+org\.vibeerp\.(platform|pbc)' "$path"; then + echo "::error::Forbidden internal import found under $path" + violations=$((violations + 1)) + fi + fi + done + if [ "$violations" -gt 0 ]; then + echo "Architectural violation: api-v1 and reference-customer may only depend on org.vibeerp.api.v1.*" + exit 1 + fi + echo "OK — no forbidden imports." diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1399ef3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,56 @@ +# vibe_erp — single shipping image (architecture spec section 10). +# +# This produces ghcr.io/vibeerp/vibe-erp:: one Spring Boot fat-jar, +# one mounted volume at /opt/vibe-erp, one mandatory dependency +# (PostgreSQL). Customer extensions live OUTSIDE the image. + +# ─── Stage 1: build ──────────────────────────────────────────────────── +# NOTE: this stage assumes ./gradlew and gradle/wrapper/* exist at the +# repo root. They are generated by Gradle itself (not hand-written) and +# will be added before anyone tries to build this image. +FROM eclipse-temurin:21-jdk-alpine AS build + +WORKDIR /workspace +COPY . . + +RUN chmod +x ./gradlew \ + && ./gradlew :distribution:bootJar --no-daemon + +# ─── Stage 2: runtime ────────────────────────────────────────────────── +FROM eclipse-temurin:21-jre-alpine AS runtime + +# Non-root user — never run the JVM as root inside the container. +RUN addgroup -S vibeerp -g 10001 \ + && adduser -S -u 10001 -G vibeerp vibeerp + +# Customer-mounted volume layout from architecture spec section 10. +RUN mkdir -p /opt/vibe-erp/config \ + /opt/vibe-erp/plugins \ + /opt/vibe-erp/i18n-overrides \ + /opt/vibe-erp/files \ + /opt/vibe-erp/logs \ + && chown -R vibeerp:vibeerp /opt/vibe-erp \ + && mkdir -p /app \ + && chown -R vibeerp:vibeerp /app + +COPY --from=build --chown=vibeerp:vibeerp \ + /workspace/distribution/build/libs/vibe-erp.jar /app/vibe-erp.jar + +WORKDIR /app + +ENV VIBEERP_PLUGINS_DIR=/opt/vibe-erp/plugins \ + VIBEERP_FILES_DIR=/opt/vibe-erp/files + +EXPOSE 8080 +VOLUME ["/opt/vibe-erp"] + +USER vibeerp + +HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \ + CMD wget -q -O /dev/null http://localhost:8080/actuator/health || exit 1 + +ENTRYPOINT ["java", \ + "-XX:+UseContainerSupport", \ + "-XX:MaxRAMPercentage=75", \ + "-Dfile.encoding=UTF-8", \ + "-jar", "/app/vibe-erp.jar"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..23752fc --- /dev/null +++ b/Makefile @@ -0,0 +1,37 @@ +# vibe_erp — developer convenience targets. +# +# Thin wrappers over Gradle and docker compose. See `make help`. + +.PHONY: help build run image up down clean test + +help: + @echo "vibe_erp — developer targets" + @echo "" + @echo " make build Compile and assemble all modules (./gradlew build)" + @echo " make run Run the distribution locally (./gradlew :distribution:bootRun)" + @echo " make image Build the local Docker image (vibeerp/vibe-erp:dev)" + @echo " make up Start the local stack (docker compose up --build)" + @echo " make down Tear down the local stack and volumes" + @echo " make clean ./gradlew clean" + @echo " make test ./gradlew test" + +build: + ./gradlew build + +run: + ./gradlew :distribution:bootRun + +image: + docker build -t vibeerp/vibe-erp:dev . + +up: + docker compose up --build + +down: + docker compose down -v + +clean: + ./gradlew clean + +test: + ./gradlew test diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..4ddbb46 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,42 @@ +# vibe_erp — local development compose stack. +# +# Brings up a Postgres + the vibe_erp image built from the local +# Dockerfile. Mirrors the architecture spec section 10 install story +# (one DB, one image, one mounted volume). + +services: + db: + image: postgres:16-alpine + environment: + POSTGRES_DB: vibeerp + POSTGRES_USER: vibeerp + POSTGRES_PASSWORD: vibeerp + volumes: + - pgdata:/var/lib/postgresql/data + ports: + - "5432:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U vibeerp"] + interval: 5s + timeout: 5s + retries: 10 + + app: + build: + context: . + dockerfile: Dockerfile + depends_on: + db: + condition: service_healthy + environment: + VIBEERP_DB_URL: jdbc:postgresql://db:5432/vibeerp + VIBEERP_DB_USER: vibeerp + VIBEERP_DB_PASSWORD: vibeerp + VIBEERP_INSTANCE_MODE: self-hosted + ports: + - "8080:8080" + volumes: + - ./local-vibeerp:/opt/vibe-erp + +volumes: + pgdata: diff --git a/local-vibeerp/.gitkeep b/local-vibeerp/.gitkeep new file mode 100644 index 0000000..c92f7ab --- /dev/null +++ b/local-vibeerp/.gitkeep @@ -0,0 +1 @@ +# Mounted into the dev container at /opt/vibe-erp diff --git a/local-vibeerp/config/.gitkeep b/local-vibeerp/config/.gitkeep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/local-vibeerp/config/.gitkeep diff --git a/local-vibeerp/config/vibe-erp.yaml b/local-vibeerp/config/vibe-erp.yaml new file mode 100644 index 0000000..2b81bcd --- /dev/null +++ b/local-vibeerp/config/vibe-erp.yaml @@ -0,0 +1,49 @@ +# vibe_erp — example mounted config (architecture spec section 10). +# +# This file documents the closed key set of vibe-erp.yaml. It is NOT +# what the dev container actually reads at runtime — application-dev.yaml +# baked into the image is. It is kept here as living documentation so +# operators can copy it as a starting point for a real deployment. + +instance: + mode: self-hosted + default-tenant: default + +database: + url: jdbc:postgresql://db:5432/vibeerp + username: vibeerp + password: vibeerp + +files: + backend: local + local-path: /opt/vibe-erp/files + +auth: + jwt: + issuer: vibe-erp + # secret is read from VIBEERP_JWT_SECRET in real deployments + oidc: + enabled: false + # issuer-uri: https://keycloak.example.com/realms/vibeerp + # client-id: vibe-erp + # client-secret: ${VIBEERP_OIDC_CLIENT_SECRET} + +i18n: + default-locale: en-US + fallback-locale: en-US + available-locales: + - en-US + - zh-CN + - de-DE + - ja-JP + - es-ES + +plugins: + directory: /opt/vibe-erp/plugins + auto-load: true + +observability: + metrics: + enabled: true + logging: + level: INFO diff --git a/local-vibeerp/files/.gitkeep b/local-vibeerp/files/.gitkeep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/local-vibeerp/files/.gitkeep diff --git a/local-vibeerp/i18n-overrides/.gitkeep b/local-vibeerp/i18n-overrides/.gitkeep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/local-vibeerp/i18n-overrides/.gitkeep diff --git a/local-vibeerp/plugins/.gitkeep b/local-vibeerp/plugins/.gitkeep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/local-vibeerp/plugins/.gitkeep -- libgit2 0.22.2