diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 70e646ddb..7822968ab 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -68,7 +68,7 @@ jobs: commit: "chore(release): version apps" title: "Release New Version" version: pnpm changeset:version - publish: pnpm changeset-publish + publish: pnpm changeset:publish createGithubReleases: false env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release_snapshot.yml b/.github/workflows/release_snapshot.yml index 1842c00a0..44040a97e 100644 --- a/.github/workflows/release_snapshot.yml +++ b/.github/workflows/release_snapshot.yml @@ -83,7 +83,7 @@ jobs: - name: Publish snapshot packages to NPM if: steps.snapshot.outputs.hasChanges == 'true' - run: pnpm changeset-publish:next + run: pnpm changeset:publish:next env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/apps/ensapi/package.json b/apps/ensapi/package.json index d20b10c19..e872968ed 100644 --- a/apps/ensapi/package.json +++ b/apps/ensapi/package.json @@ -18,7 +18,8 @@ "lint": "biome check --write .", "lint:ci": "biome ci", "typecheck": "tsgo --noEmit", - "generate:gqlschema": "tsx src/omnigraph-api/lib/write-graphql-schema.ts" + "generate:gqlschema": "tsx src/omnigraph-api/lib/write-graphql-schema.ts", + "version:current": "node -p \"require('./package.json').version\"" }, "dependencies": { "@ensdomains/ensjs": "^4.0.2", diff --git a/docker/README.md b/docker/README.md index ac8e8f6ba..61c19a9fb 100644 --- a/docker/README.md +++ b/docker/README.md @@ -6,11 +6,11 @@ All commands are run from the **monorepo root**. | File | Purpose | | ---------------------------------------- | -------------------------------------------------------------------------------------- | -| `docker/docker-compose.yml` | Base stack — ensindexer, ensapi, ensrainbow, ensadmin, postgres. For mainnet/sepolia. | +| `docker/docker-compose.yml` | Base stack — ensindexer, ensapi, ensrainbow, ensadmin, ensdb. For mainnet/sepolia. | | `docker/docker-compose.devnet.yml` | Full stack against local devnet (`ens-test-env`). Includes all base services + devnet. | -| `docker/docker-compose.orchestrator.yml` | Minimal infra for CI — devnet + postgres only. Used by `orchestrator.ts`. | +| `docker/docker-compose.orchestrator.yml` | Minimal infra for CI — devnet + ensdb only. Used by `orchestrator.ts`. | | `docker/services/*.yml` | Individual service definitions. Extended by the compose files above. | -| `docker/envs/.env.docker.common` | Shared env defaults (postgres credentials, internal service URLs). Committed. | +| `docker/envs/.env.docker.common` | Shared env defaults (ensdb credentials, internal service URLs). Committed. | | `docker/envs/.env.docker.devnet` | Devnet defaults (PLUGINS, etc.). Committed. Works out of the box. | | `docker/envs/.env.docker.example` | Example for user-specific config. Copy to `.env.docker.local` for mainnet/sepolia. | | `docker/envs/.env.docker.local` | User config (gitignored). Required for base stack, optional for devnet overrides. | @@ -57,7 +57,7 @@ To override defaults (e.g. change `PLUGINS`), create `docker/envs/.env.docker.lo docker compose -f docker/docker-compose.devnet.yml up -d # Start only devnet + core services (no ensadmin) -docker compose -f docker/docker-compose.devnet.yml up -d devnet postgres ensrainbow ensindexer ensapi +docker compose -f docker/docker-compose.devnet.yml up -d ensindexer ensapi # Start only devnet (quick local EVM node, also shows data information about devnet) docker compose -f docker/docker-compose.devnet.yml up devnet @@ -83,7 +83,7 @@ pnpm docker:build:ensadmin ### CI / integration tests -Used internally by `orchestrator.ts` via testcontainers. Starts devnet + postgres only. +Used internally by `orchestrator.ts` via testcontainers. Starts devnet + ensdb only. ```bash pnpm test:integration:ci diff --git a/docker/docker-compose.devnet.yml b/docker/docker-compose.devnet.yml index 03bfb243c..5f2e60852 100644 --- a/docker/docker-compose.devnet.yml +++ b/docker/docker-compose.devnet.yml @@ -4,12 +4,6 @@ services: file: services/ensindexer.yml service: ensindexer environment: - # TODO: in future we will migrate devnet to chain_id=1 - # need to remove `RPC_URL_15658733` in that case - RPC_URL_15658733: http://devnet:8545 - RPC_URL_1: http://devnet:8545 - ENSINDEXER_SCHEMA_NAME: docker_devnet_v1 - LABEL_SET_ID: ens-test-env NAMESPACE: ens-test-env env_file: - path: envs/.env.docker.common @@ -21,7 +15,7 @@ services: depends_on: ensrainbow: condition: service_healthy - postgres: + ensdb: condition: service_healthy devnet: condition: service_healthy @@ -30,14 +24,8 @@ services: extends: file: services/ensapi.yml service: ensapi - environment: - # TODO: in future we will migrate devnet to chain_id=1 - # need to remove `RPC_URL_15658733` in that case - RPC_URL_15658733: http://devnet:8545 - RPC_URL_1: http://devnet:8545 - ENSINDEXER_SCHEMA_NAME: docker_devnet_v1 depends_on: - postgres: + ensdb: condition: service_healthy env_file: - path: envs/.env.docker.common @@ -51,8 +39,6 @@ services: extends: file: services/ensrainbow.yml service: ensrainbow - environment: - LABEL_SET_ID: ens-test-env env_file: - path: envs/.env.docker.common required: true @@ -76,12 +62,12 @@ services: - path: envs/.env.docker.local required: false - postgres: + ensdb: extends: - file: services/postgres.yml - service: postgres + file: services/ensdb.yml + service: ensdb env_file: - - path: ./envs/.env.docker.common + - path: envs/.env.docker.common required: true devnet: @@ -94,8 +80,8 @@ volumes: # compose file that references them — they cannot be inherited via `extends`. # Explicit `name:` prevents collision with the base stack's volumes when both # are run from the same directory (same project name). - postgres_data: - name: ensnode_devnet_postgres_data + ensdb_data: + name: ensnode_devnet_ensdb_data driver: local ensrainbow_data: name: ensnode_devnet_ensrainbow_data diff --git a/docker/docker-compose.orchestrator.yml b/docker/docker-compose.orchestrator.yml index e813e27f6..82290a79e 100644 --- a/docker/docker-compose.orchestrator.yml +++ b/docker/docker-compose.orchestrator.yml @@ -1,22 +1,22 @@ # Minimal compose for CI integration tests. # Provides only the infrastructure services needed by orchestrator.ts: -# devnet (local EVM) and postgres (database). +# devnet (local EVM) and ensdb (database). services: devnet: extends: file: services/devnet.yml service: devnet - postgres: + ensdb: extends: - file: services/postgres.yml - service: postgres + file: services/ensdb.yml + service: ensdb env_file: - - path: ./envs/.env.docker.common + - path: envs/.env.docker.common required: true volumes: # Docker Compose requires volumes used by services to be declared in each # compose file that references them — they cannot be inherited via `extends`. - postgres_data: + ensdb_data: driver: local diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 32bddab5d..65b348d8d 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -3,12 +3,13 @@ services: extends: file: services/ensindexer.yml service: ensindexer + restart: unless-stopped environment: ENSINDEXER_SCHEMA_NAME: docker_ensindexer_v1 depends_on: ensrainbow: condition: service_healthy - postgres: + ensdb: condition: service_healthy env_file: - path: envs/.env.docker.common @@ -21,10 +22,11 @@ services: extends: file: services/ensapi.yml service: ensapi + restart: unless-stopped environment: ENSINDEXER_SCHEMA_NAME: docker_ensindexer_v1 depends_on: - postgres: + ensdb: condition: service_healthy env_file: - path: envs/.env.docker.common @@ -36,6 +38,7 @@ services: extends: file: services/ensrainbow.yml service: ensrainbow + restart: unless-stopped env_file: - path: envs/.env.docker.common required: true @@ -46,6 +49,7 @@ services: extends: file: services/ensadmin.yml service: ensadmin + restart: unless-stopped depends_on: ensapi: condition: service_started @@ -55,10 +59,11 @@ services: - path: envs/.env.docker.local required: true - postgres: + ensdb: extends: - file: services/postgres.yml - service: postgres + file: services/ensdb.yml + service: ensdb + restart: unless-stopped env_file: - path: envs/.env.docker.common required: true @@ -66,7 +71,7 @@ services: volumes: # Docker Compose requires volumes used by services to be declared in each # compose file that references them — they cannot be inherited via `extends`. - postgres_data: + ensdb_data: driver: local ensrainbow_data: driver: local diff --git a/docker/envs/.env.docker.common b/docker/envs/.env.docker.common index d8c682539..1d8713c9e 100644 --- a/docker/envs/.env.docker.common +++ b/docker/envs/.env.docker.common @@ -1,16 +1,15 @@ # Shared Docker Compose environment variables # Used by docker/docker-compose.yml and its variants - # Postgres POSTGRES_DB=postgres POSTGRES_USER=postgres POSTGRES_PASSWORD=password # Internal service URLs (container-to-container) -ENSDB_URL=postgresql://postgres:password@postgres:5432/postgres +ENSDB_URL=postgresql://postgres:password@ensdb:5432/postgres ENSRAINBOW_URL=http://ensrainbow:3223 -# ENS Admin +# ENSAdmin ENSADMIN_PUBLIC_URL=http://localhost:4173 NEXT_PUBLIC_SERVER_CONNECTION_LIBRARY=http://localhost:4334 diff --git a/docker/envs/.env.docker.devnet b/docker/envs/.env.docker.devnet index 7c63beef8..20eb13b77 100644 --- a/docker/envs/.env.docker.devnet +++ b/docker/envs/.env.docker.devnet @@ -1,6 +1,18 @@ # Default configuration for devnet docker-compose stack. # These values work out of the box — override by creating .env.docker.local. +# ENSIndexer PLUGINS=subgraph,ensv2 +# ENSIndexer and ENSApi +ENSINDEXER_SCHEMA_NAME=docker_devnet_v1 +# ENSIndexer and ENSApi +RPC_URL_1=http://devnet:8545 +# ENSIndexer and ENSRainbow LABEL_SET_VERSION=0 +# ENSIndexer and ENSRainbow +LABEL_SET_ID=ens-test-env +# ENSRainbow DB_SCHEMA_VERSION=3 +# TODO: in future we might migrate devnet to different chain_id like 1337 or 31337 +# need to update this `RPC_URL_15658733` in that case +RPC_URL_15658733=http://devnet:8545 diff --git a/docker/services/ensadmin.yml b/docker/services/ensadmin.yml index fbc0fd542..a47f8b568 100644 --- a/docker/services/ensadmin.yml +++ b/docker/services/ensadmin.yml @@ -1,7 +1,7 @@ services: ensadmin: container_name: ensadmin - image: ghcr.io/namehash/ensnode/ensadmin:latest + image: ghcr.io/namehash/ensnode/ensadmin:${ENSNODE_VERSION:-1.10.1} build: dockerfile: ./apps/ensadmin/Dockerfile context: ../.. diff --git a/docker/services/ensapi.yml b/docker/services/ensapi.yml index 71ce5c7d3..0d7eafa36 100644 --- a/docker/services/ensapi.yml +++ b/docker/services/ensapi.yml @@ -1,7 +1,7 @@ services: ensapi: container_name: ensapi - image: ghcr.io/namehash/ensnode/ensapi:latest + image: ghcr.io/namehash/ensnode/ensapi:${ENSNODE_VERSION:-1.10.1} build: dockerfile: ./apps/ensapi/Dockerfile context: ../.. diff --git a/docker/services/postgres.yml b/docker/services/ensdb.yml similarity index 74% rename from docker/services/postgres.yml rename to docker/services/ensdb.yml index 85f0f12b2..78554a630 100644 --- a/docker/services/postgres.yml +++ b/docker/services/ensdb.yml @@ -1,11 +1,11 @@ services: - postgres: - container_name: postgres + ensdb: + container_name: ensdb image: postgres:17 ports: - "5432:5432" volumes: - - postgres_data:/var/lib/postgresql/data + - ensdb_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] interval: 5s diff --git a/docker/services/ensindexer.yml b/docker/services/ensindexer.yml index ac472ddaf..6f5e08088 100644 --- a/docker/services/ensindexer.yml +++ b/docker/services/ensindexer.yml @@ -1,7 +1,7 @@ services: ensindexer: container_name: ensindexer - image: ghcr.io/namehash/ensnode/ensindexer:latest + image: ghcr.io/namehash/ensnode/ensindexer:${ENSNODE_VERSION:-1.10.1} build: dockerfile: ./apps/ensindexer/Dockerfile context: ../.. diff --git a/docker/services/ensrainbow.yml b/docker/services/ensrainbow.yml index 5e1c3c726..66be616d7 100644 --- a/docker/services/ensrainbow.yml +++ b/docker/services/ensrainbow.yml @@ -1,7 +1,7 @@ services: ensrainbow: container_name: ensrainbow - image: ghcr.io/namehash/ensnode/ensrainbow:latest + image: ghcr.io/namehash/ensnode/ensrainbow:${ENSNODE_VERSION:-1.10.1} build: dockerfile: ./apps/ensrainbow/Dockerfile context: ../.. @@ -9,7 +9,6 @@ services: - "3223:3223" volumes: - ensrainbow_data:/app/apps/ensrainbow/data - restart: unless-stopped healthcheck: test: ["CMD", "wget", "-q", "--spider", "http://localhost:3223/health"] interval: 30s diff --git a/docs/ensnode.io/src/content/docs/docs/deploying/docker.mdx b/docs/ensnode.io/src/content/docs/docs/deploying/docker.mdx index 46196b1e2..cd3595e32 100644 --- a/docs/ensnode.io/src/content/docs/docs/deploying/docker.mdx +++ b/docs/ensnode.io/src/content/docs/docs/deploying/docker.mdx @@ -20,7 +20,7 @@ ENSIndexer runs `CREATE EXTENSION IF NOT EXISTS pg_trgm` at startup to back part ENSNode provides several [Docker Compose](https://docs.docker.com/compose/) files for different use cases: -- **`docker/docker-compose.yml`** — base stack for mainnet/sepolia: ensindexer, ensapi, ensrainbow, ensadmin, postgres +- **`docker/docker-compose.yml`** — base stack for mainnet/sepolia: ensindexer, ensapi, ensrainbow, ensadmin, ensdb (postgres) - **`docker/docker-compose.devnet.yml`** — full stack against local devnet (ens-test-env), works out of the box with no configuration required ### Mainnet / Sepolia diff --git a/package.json b/package.json index 7b643b296..cb3f8d5a6 100644 --- a/package.json +++ b/package.json @@ -12,13 +12,15 @@ "audit:osv": "osv-scanner scan source --lockfile pnpm-lock.yaml", "typecheck": "pnpm -r --parallel --aggregate-output typecheck", "changeset": "changeset", - "changeset:version": "changeset version && pnpm generate:openapi", - "changeset:next": "changeset version --snapshot next && pnpm generate:openapi", - "changeset-publish": "changeset publish", - "changeset-publish:next": "changeset publish --no-git-tag --snapshot --tag next", + "changeset:version": "changeset version && pnpm release:postversion", + "changeset:next": "changeset version --snapshot next && pnpm release:postversion", + "changeset:publish": "changeset publish", + "changeset:publish:next": "changeset publish --no-git-tag --snapshot --tag next", + "release:postversion": "pnpm docker:version:sync && pnpm generate:openapi", "packages:prepublish": "pnpm -r prepublish", "devnet": "docker compose -f docker/docker-compose.devnet.yml up devnet", "docker:build:ensnode": "pnpm run -w --parallel \"/^docker:build:.*/\"", + "docker:version:sync": "node ./scripts/sync-docker-services-tags.mjs \"$(pnpm -F ensapi -s version:current)\"", "docker:build:ensindexer": "docker build -f apps/ensindexer/Dockerfile -t ghcr.io/namehash/ensnode/ensindexer:latest .", "docker:build:ensadmin": "docker build -f apps/ensadmin/Dockerfile -t ghcr.io/namehash/ensnode/ensadmin:latest .", "docker:build:ensrainbow": "docker build -f apps/ensrainbow/Dockerfile -t ghcr.io/namehash/ensnode/ensrainbow:latest .", diff --git a/packages/integration-test-env/src/orchestrator.ts b/packages/integration-test-env/src/orchestrator.ts index 17e2a72d5..9b177eaae 100644 --- a/packages/integration-test-env/src/orchestrator.ts +++ b/packages/integration-test-env/src/orchestrator.ts @@ -5,14 +5,14 @@ * monorepo-level integration tests, then tears everything down. * * Phases: - * 1. Postgres + devnet via docker-compose (testcontainers DockerComposeEnvironment) + * 1. ENSDb (postgres) + devnet via docker-compose (testcontainers DockerComposeEnvironment) * 2. Download pre-built ENSRainbow LevelDB, extract, start ENSRainbow from source * 3. Start ENSIndexer, wait for omnichain-following / omnichain-completed * 4. Start ENSApi * 5. Run `pnpm test:integration` at the monorepo root * * Design decisions: - * - Postgres and devnet are started from docker/docker-compose.orchestrator.yml via + * - ENSDb (postgres) and devnet are started from docker/docker-compose.orchestrator.yml via * testcontainers DockerComposeEnvironment, ensuring the orchestrator always * uses the same images and configuration defined there. * - execa for child process management — automatic cleanup on parent exit, @@ -22,7 +22,7 @@ * - ENSRainbow database is downloaded via the existing shell script and * extracted with tar, mirroring the Docker entrypoint behavior. * - Cleanup stops processes in reverse order (ensapi → ensindexer → ensrainbow) - * so DB consumers close connections before Postgres is stopped. + * so DB consumers close connections before ensdb is stopped. * - Abort flag pattern: if a background service crashes during polling/health * checks, the orchestrator fails fast instead of waiting for a timeout. * - SIGINT/SIGTERM handler is guarded against re-entrance (repeated Ctrl-C). @@ -237,21 +237,21 @@ async function main() { log("Starting integration test environment..."); logVersions(); - // Phase 1: Start Postgres + Devnet via docker-compose - log("Starting Postgres and devnet..."); + // Phase 1: Start ENSDb + Devnet via docker-compose + log("Starting ENSDb and Devnet..."); composeEnvironment = await new DockerComposeEnvironment( DOCKER_DIR, "docker-compose.orchestrator.yml", ) .withWaitStrategy("devnet", Wait.forHealthCheck()) - .withWaitStrategy("postgres", Wait.forListeningPorts()) + .withWaitStrategy("ensdb", Wait.forListeningPorts()) .withStartupTimeout(120_000) - .up(["postgres", "devnet"]); + .up(["ensdb", "devnet"]); - const postgresContainer = composeEnvironment.getContainer("postgres"); - const postgresPort = postgresContainer.getMappedPort(5432); - const ENSDB_URL = `postgresql://postgres:password@localhost:${postgresPort}/postgres`; - log(`Postgres is ready (port ${postgresPort})`); + const ensdbContainer = composeEnvironment.getContainer("ensdb"); + const ensdbPort = ensdbContainer.getMappedPort(5432); + const ENSDB_URL = `postgresql://postgres:password@localhost:${ensdbPort}/postgres`; + log(`ENSDb is ready (port ${ensdbPort})`); log("Devnet is ready"); // Phase 2: Download ENSRainbow database and start from source diff --git a/scripts/sync-docker-services-tags.mjs b/scripts/sync-docker-services-tags.mjs new file mode 100644 index 000000000..22f13f797 --- /dev/null +++ b/scripts/sync-docker-services-tags.mjs @@ -0,0 +1,66 @@ +// Keeps Docker compose service defaults aligned with the current ENSNode release version. +// Responsibility: +// - update `${ENSNODE_VERSION:-...}` fallbacks in docker service definitions +// - validate that the provided version looks like SemVer before writing files + +// Usage: +// node ./scripts/sync-docker-services-tags.mjs 1.10.1-beta.1 + +import { readFile, writeFile } from "node:fs/promises"; +import { resolve } from "node:path"; + +const rootDir = resolve(import.meta.dirname, ".."); + +const serviceFiles = [ + "docker/services/ensadmin.yml", + "docker/services/ensapi.yml", + "docker/services/ensindexer.yml", + "docker/services/ensrainbow.yml", +]; + +const semverRegex = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/; + +function validateVersion(version) { + if (!semverRegex.test(version)) { + throw new Error(`Invalid version "${version}". Expected SemVer-like value such as 1.10.1`); + } +} + +async function updateServiceDefaultVersion(version) { + const testPattern = /\$\{ENSNODE_VERSION:-[^}]+\}/; + const replacePattern = /\$\{ENSNODE_VERSION:-[^}]+\}/g; + const replacement = `\${ENSNODE_VERSION:-${version}}`; + + console.log(`Updating service default tag to ${version}\n`); + + for (const relativePath of serviceFiles) { + const absolutePath = resolve(rootDir, relativePath); + const content = await readFile(absolutePath, "utf8"); + + if (!testPattern.test(content)) { + throw new Error(`Could not find ENSNODE_VERSION default expression in ${relativePath}`); + } + + const previous = content.match(testPattern)[0]; + const updated = content.replace(replacePattern, replacement); + await writeFile(absolutePath, updated, "utf8"); + console.log(`Updated ${relativePath}:\t"${previous}" -> "${replacement}"`); + } +} + +async function main() { + const versionFromArg = process.argv[2]; + if (typeof versionFromArg !== "string" || versionFromArg.length === 0) { + throw new Error( + "Version argument is required. Usage: node scripts/sync-docker-services-tags.mjs ", + ); + } + + validateVersion(versionFromArg); + await updateServiceDefaultVersion(versionFromArg); +} + +main().catch((error) => { + console.error(error instanceof Error ? error.message : String(error)); + process.exit(1); +});