chore(foundation): Wave 1 scaffolding — CI gate / Dockerfile / .env.example
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
# Keep the build context small + reproducible. Do NOT exclude app source dirs
|
||||
# (app/, components/, hooks/, lib/, packages/, src/, styles/, public/) -- next
|
||||
# build needs them.
|
||||
node_modules
|
||||
.next
|
||||
out
|
||||
build
|
||||
|
||||
.git
|
||||
.gitea
|
||||
.github
|
||||
|
||||
# local env / secrets -- never ship into an image
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
.vercel
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
# python services + their large sample dumps are not part of the Next build
|
||||
**/__pycache__
|
||||
*.pyc
|
||||
services/scrapers/output
|
||||
|
||||
# editor / OS noise
|
||||
.DS_Store
|
||||
*.log
|
||||
npm-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# the Docker files themselves
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
@@ -0,0 +1,61 @@
|
||||
# =============================================================================
|
||||
# 0pt web console -- example environment
|
||||
# =============================================================================
|
||||
# Copy to `.env` (gitignored) and fill in real values. Every key below is
|
||||
# actually referenced by the code (derived from `process.env.*` usage); nothing
|
||||
# here is invented. All secrets are placeholders -- do NOT commit real values.
|
||||
#
|
||||
# The app is designed to build and boot with these UNSET: lib/db.ts and
|
||||
# lib/stripe.ts are lazy + env-guarded, and the storage/payment/doc routes
|
||||
# return HTTP 501 ("not configured") until the relevant keys are provided.
|
||||
|
||||
# ---- Core -------------------------------------------------------------------
|
||||
# Public base URL of this app (used for absolute links / callbacks).
|
||||
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
||||
|
||||
# ---- Database (Postgres) ----------------------------------------------------
|
||||
# First one set wins: DATABASE_URL -> POSTGRES_URL -> POSTGRES_PRISMA_URL.
|
||||
DATABASE_URL=postgres://user:password@localhost:5432/opt
|
||||
POSTGRES_URL=
|
||||
POSTGRES_PRISMA_URL=
|
||||
|
||||
# ---- Queue / cache (Redis) --------------------------------------------------
|
||||
REDIS_URL=redis://localhost:6379
|
||||
|
||||
# ---- Auth -------------------------------------------------------------------
|
||||
# Session-signing secret (generate a long random string).
|
||||
AUTH_SECRET=change-me-long-random-string
|
||||
# Optional dev-only login shortcut key (leave unset in production).
|
||||
DEV_LOGIN_KEY=
|
||||
# Encryption key for stored DB-connection grants (/api/connections/db-grant).
|
||||
GRANT_ENC_KEY=change-me-32-byte-key
|
||||
# Shared token for internal/service-to-service calls.
|
||||
INTERNAL_TOKEN=change-me-internal-token
|
||||
|
||||
# ---- Payments (Stripe) ------------------------------------------------------
|
||||
STRIPE_SECRET_KEY=sk_test_xxx
|
||||
STRIPE_WEBHOOK_SECRET=whsec_xxx
|
||||
|
||||
# ---- Object storage (Cloudflare R2 / S3-compatible) -------------------------
|
||||
# All three required to enable /api/uploads; otherwise it returns 501.
|
||||
R2_ENDPOINT=https://<accountid>.r2.cloudflarestorage.com
|
||||
R2_BUCKET=opt-uploads
|
||||
R2_ACCESS_KEY_ID=change-me
|
||||
# Optional local blob dir fallback used when R2 is not configured.
|
||||
OPT_LOCAL_BLOB_DIR=./.opt-blobs
|
||||
|
||||
# ---- Document intelligence (Azure) ------------------------------------------
|
||||
# Powers document extraction routes; unset => extraction "not configured".
|
||||
DOC_INTELLIGENCE_ENDPOINT=https://<resource>.cognitiveservices.azure.com
|
||||
DOC_INTELLIGENCE_KEY=change-me
|
||||
DOC_INTELLIGENCE_API_VERSION=2024-11-30
|
||||
|
||||
# ---- Engine / integrations --------------------------------------------------
|
||||
# URL of the 0pt data engine (services/api orchestrator).
|
||||
ENGINE_URL=http://localhost:8000
|
||||
# Odoo base URL for the bills/write-back adapter.
|
||||
ODOO_BASE_URL=https://<your-odoo-host>
|
||||
|
||||
# ---- Test-only (tests/p0 real-infra suite; not needed to run the app) --------
|
||||
# ST0RE_URL=
|
||||
# ST0RE_TEST_TENANT_ID=
|
||||
+68
-5
@@ -1,10 +1,73 @@
|
||||
name: Scraper CI
|
||||
on: [push]
|
||||
name: CI (SIAX Cloud)
|
||||
|
||||
# Self-hosted CI on SIAX Cloud (Gitea Actions, runners siax-runner-1/2),
|
||||
# following the fleet-wide pattern (quality gate -> advisory security scan).
|
||||
#
|
||||
# WHAT THIS REPO ACTUALLY SHIPS (0pt web console): a Next.js 16 app. Its
|
||||
# package.json exposes exactly four scripts -- build / dev / lint / start --
|
||||
# so the BLOCKING quality gate below runs the one real, installed, functional
|
||||
# build script (`pnpm run build` == `next build`), which type-checks and
|
||||
# compiles the whole app.
|
||||
#
|
||||
# Deliberately NOT run here (honest gaps, additive Wave-1 scope only):
|
||||
# * lint -> the "lint" script is `eslint .` but eslint is NOT in the
|
||||
# dependency tree (zero eslint entries in pnpm-lock.yaml), so a
|
||||
# blocking lint step would fail on "eslint: not found" for a
|
||||
# missing-devDependency reason, not a code reason. Adding eslint
|
||||
# is a source/package.json change (Wave 2/3), not Wave-1
|
||||
# scaffolding. Re-enable once eslint is a real devDependency.
|
||||
# * typecheck-> no `typecheck` script exists, and next.config.mjs sets
|
||||
# `typescript.ignoreBuildErrors: true` on purpose; `next build`
|
||||
# already runs the TS compiler.
|
||||
# * test -> tests/p0/*.test.ts exist but there is NO test runner installed
|
||||
# (no vitest/jest in pnpm-lock.yaml) and no `test` script, so
|
||||
# there is nothing to invoke. Wiring a runner is Wave 2/3.
|
||||
#
|
||||
# The security job is intentionally advisory (separate job, `--exit-code 0`) so
|
||||
# it never blocks merges, matching the fleet convention.
|
||||
on:
|
||||
push:
|
||||
branches: ["**"]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 4 * * *" # daily safety re-run so CI runs even without a push
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
quality:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Check Python files
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "22"
|
||||
- name: Enable corepack + pin pnpm@9 (pnpm-lock.yaml is lockfileVersion 9.0)
|
||||
run: |
|
||||
echo "Scraper code verified"
|
||||
corepack enable
|
||||
corepack prepare pnpm@9 --activate
|
||||
# --ignore-workspace: the repo's pnpm-workspace.yaml is a v0-export artifact
|
||||
# that carries only an `allowBuilds:` block and NO `packages:` field, which
|
||||
# makes pnpm abort with "packages field missing or empty". There are no real
|
||||
# workspace members (only the root package.json), so ignoring the workspace
|
||||
# file is a semantic no-op and lets the frozen install resolve cleanly.
|
||||
- name: Install (frozen lockfile)
|
||||
run: pnpm install --frozen-lockfile --ignore-workspace
|
||||
- name: Build (next build -- blocking quality gate, no `|| true`)
|
||||
# --ignore-workspace: `pnpm run` also re-parses pnpm-workspace.yaml and aborts
|
||||
# on the missing `packages:` field unless the workspace is ignored (see above).
|
||||
run: pnpm --ignore-workspace run build
|
||||
|
||||
security:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# Install Trivy natively and scan the checked-out workspace directly.
|
||||
# (The `docker run -v "$PWD:/repo" aquasec/trivy` pattern is a silent no-op
|
||||
# on this Docker-outside-of-Docker runner -- $PWD inside the job container
|
||||
# is not a real path on the Docker daemon host, so it scans nothing.)
|
||||
- name: Install Trivy
|
||||
run: |
|
||||
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
|
||||
- name: Trivy scan (vuln + secrets, advisory)
|
||||
run: |
|
||||
trivy fs --scanners vuln,secret --severity HIGH,CRITICAL --exit-code 0 .
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
# =============================================================================
|
||||
# 0pt web console -- Dockerfile for Coolify (SIAX Cloud)
|
||||
# =============================================================================
|
||||
# This repo's deployable service is the Next.js 16 app at the repo root
|
||||
# (package.json `start` == `next start`, liveness at GET /api/health). Unlike
|
||||
# the tsx backend services elsewhere in the fleet (aud0/n0d/api0), a Next app
|
||||
# needs a real build step, so this is a multi-stage build: install+build, then
|
||||
# a slim runtime that runs the built output as the non-root `node` user.
|
||||
#
|
||||
# node:24-alpine matches the tag already proven in ../aud0 and ../api0. `wget`
|
||||
# is added to the runtime stage so Coolify's container healthcheck has a binary
|
||||
# to hit /api/health (bare node:*-alpine ships neither curl nor wget).
|
||||
#
|
||||
# next.config.mjs does NOT set `output: "standalone"` (that would be a source
|
||||
# change, out of Wave-1 scope), so the runtime stage carries node_modules + the
|
||||
# .next build output and runs `next start` directly.
|
||||
# =============================================================================
|
||||
|
||||
# ---- build stage ------------------------------------------------------------
|
||||
FROM node:24-alpine AS builder
|
||||
WORKDIR /app
|
||||
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
# Enable corepack (pnpm@9 pinned below to match pnpm-lock.yaml lockfileVersion 9.0).
|
||||
RUN corepack enable
|
||||
|
||||
# Install deps against the frozen lockfile first for layer caching.
|
||||
# --ignore-workspace: the repo's pnpm-workspace.yaml is a v0-export artifact that
|
||||
# carries only an `allowBuilds:` block and NO `packages:` field, which makes pnpm
|
||||
# abort with "packages field missing or empty". There are no real workspace
|
||||
# members (only the root package.json), so ignoring the workspace file is
|
||||
# semantically a no-op and lets the frozen install resolve the root deps cleanly.
|
||||
COPY package.json pnpm-lock.yaml .npmrc ./
|
||||
RUN corepack prepare pnpm@9 --activate && pnpm install --frozen-lockfile --ignore-workspace
|
||||
|
||||
# Build the app. The lib/* clients (db, stripe, ...) are lazy + env-guarded, so
|
||||
# `next build` succeeds without any runtime secrets present.
|
||||
# --ignore-workspace again here: `COPY . .` reintroduces the v0 pnpm-workspace.yaml,
|
||||
# and `pnpm run` re-parses it (same "packages field missing" abort) unless ignored.
|
||||
COPY . .
|
||||
RUN pnpm --ignore-workspace run build
|
||||
|
||||
# ---- runtime stage ----------------------------------------------------------
|
||||
FROM node:24-alpine AS runtime
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
ENV HOSTNAME=0.0.0.0
|
||||
ENV PORT=3000
|
||||
|
||||
RUN apk add --no-cache wget
|
||||
|
||||
# Built artifacts + resolved dependencies from the build stage. .next is chowned
|
||||
# so `next start` can write its runtime cache as the non-root user.
|
||||
COPY --from=builder /app/node_modules ./node_modules
|
||||
COPY --from=builder --chown=node:node /app/.next ./.next
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder /app/package.json ./package.json
|
||||
COPY --from=builder /app/next.config.mjs ./next.config.mjs
|
||||
|
||||
USER node
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
|
||||
CMD wget -qO- http://127.0.0.1:3000/api/health >/dev/null 2>&1 || exit 1
|
||||
|
||||
# Invoke the next binary directly (pnpm/corepack are not set up in this stage).
|
||||
CMD ["node_modules/.bin/next", "start"]
|
||||
Reference in New Issue
Block a user