diff --git a/PACKAGE-INSTALL.md b/PACKAGE-INSTALL.md index 381076c..27c2ce7 100644 --- a/PACKAGE-INSTALL.md +++ b/PACKAGE-INSTALL.md @@ -2,6 +2,30 @@ Repository-backed installation commands for DocumentDB. +> This repository serves v0.117-0. v1.0-RC1 is for testing only, gets no fixes (those go into +> RC2 or 1.0), has no upgrade path, and isn't in this repository; see +> [Try the 1.0 release candidate](https://documentdb.io/docs/getting-started/release-candidate/). + +## Clean-host installer + +On a clean Ubuntu 24.04 or EL9 host, download and run the installer: + +```sh +curl -fsSLo documentdb-install.sh https://documentdb.io/install.sh && +sh documentdb-install.sh +``` + +The default installs from the stable repository, currently v0.117-0. +For disposable RC1 testing, run `sh documentdb-install.sh --version v1.0-RC1` +instead. Both modes default to PostgreSQL 18; use `--pg-major 17` to select 17. +The RC path downloads checksum-verified release assets without putting them in +the stable repository. Existing DocumentDB packages, configuration, or data +are refused for RC installation. + +The website publishes the canonical engine installer using the commit and +checksum pinned in `scripts/publish-installer.mjs`. This pin is independent +of `DOCUMENTDB_VERSION`, which selects the stable package mirror. + ## What is published Starting with **v0.116-0**, DocumentDB ships a multi-package layout with a setup wizard and diff --git a/app/services/articleService.ts b/app/services/articleService.ts index 170db89..490a064 100644 --- a/app/services/articleService.ts +++ b/app/services/articleService.ts @@ -190,8 +190,28 @@ The current official release publishes the full stack — extension, gateway, se > [!NOTE] > Need another distribution or PostgreSQL major? We welcome community builds. Check out the matching release tag and use the version-parameterized [packaging scripts](https://github.com/documentdb/documentdb/blob/v0.117-0/packaging/README.md). \`build_packages.sh\` builds the extension, \`gateway/build_gateway_packages.sh\` builds the gateway, and \`build_extra_packages.sh\` builds the common, tools, stand-alone, and meta packages. PostgreSQL 15 is extension-only because the setup tools require PostgreSQL 16 or newer. These builds are on demand and are not official release assets hosted by documentdb.io. +> [!NOTE] +> Want to try DocumentDB 1.0? [v1.0-RC1](/docs/getting-started/release-candidate) is for testing only, gets no fixes (those go into RC2 or 1.0) and has no upgrade path. It isn't in the package repository, so the commands below install v0.117-0. + You do not need PostgreSQL already installed — the setup wizard creates and manages its own instance. The install does add the PGDG repository and pull PostgreSQL, PostGIS and around 160 packages (about 140 MB), so pick a host you are willing to have PGDG on. +## Clean-host installer + +On a fresh Ubuntu 24.04 or EL9 host, download and run the installer: + +\`\`\`sh +curl -fsSLo documentdb-install.sh https://documentdb.io/install.sh && +sh documentdb-install.sh +\`\`\` + +This defaults to the stable repository, currently v0.117-0, and PostgreSQL 18. +Use \`--pg-major 17\` to select PostgreSQL 17. For disposable RC1 testing, +run \`sh documentdb-install.sh --version v1.0-RC1\` instead. The RC path +downloads checksum-verified release assets and refuses existing DocumentDB +packages, configuration, or data. RC1 has no maintenance or supported upgrades. +See [RC instructions](/docs/getting-started/release-candidate) for details. +The manual repository commands below remain on the stable channel. + ## Install ### Ubuntu 24.04, PostgreSQL 18 (APT) @@ -392,7 +412,9 @@ It does **not** install the gateway, create a network endpoint for drivers, or r Use the extension package for your PostgreSQL major: \`postgresql-N-documentdb\` on Ubuntu or \`postgresqlN-documentdb\` on EL9. You own PostgreSQL configuration, extension activation, -and service restarts. Follow the matching release's [manual package instructions](https://github.com/documentdb/documentdb/blob/v0.117-0/packaging/README.md), +and service restarts. Create \`documentdb_extended_rum\` as well as \`documentdb\`: +\`CREATE EXTENSION documentdb CASCADE\` does not pull it in, and without it every index +creation fails. Follow the matching release's [manual package instructions](https://github.com/documentdb/documentdb/blob/v0.117-0/packaging/README.md), or the [extension-only offline instructions](/docs/linux-packages/offline#smaller-offline-cases) when PostgreSQL and all extension dependencies are already installed. @@ -1239,6 +1261,7 @@ The \`latest\` tag is a convenience alias. Pin an explicit tag for anything repr | \`ghcr.io/documentdb/documentdb/documentdb-local:pg18-0.117.0\` | DocumentDB 0.117.0 on PostgreSQL 18 | | \`…:pg17-0.117.0\` | DocumentDB 0.117.0 on PostgreSQL 17 | | \`…:pg16-0.117.0\` · \`…:pg15-0.117.0\` | PostgreSQL 16 and 15 | +| \`…:pg15-1.0.0-rc1\` · \`…:pg16-1.0.0-rc1\` · \`…:pg17-1.0.0-rc1\` · \`…:pg18-1.0.0-rc1\` | [v1.0-RC1](https://github.com/documentdb/documentdb/releases/tag/v1.0-RC1), for testing only, with [no fixes to RC1 and no upgrade path](/docs/getting-started/release-candidate). No date for 1.0 yet. Use a new, empty data volume. The \`pgNN-1.0.0\` tags point at the same images today but will move to the final 1.0 build. | | \`…:latest\` | Currently identical to \`pg17-0.117.0\` | > \`latest\` tracks **PostgreSQL 17**, while the \`documentdb\` package on Linux pins diff --git a/package.json b/package.json index 0527c10..e14e0e8 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "private": true, "scripts": { "dev": "npm run compile && next dev --turbopack", - "build": "npm run build:next && npm run build:blogs && npm run build:sitemap", + "build": "npm run build:next && npm run build:blogs && npm run build:sitemap && npm run build:installer", + "build:installer": "node scripts/publish-installer.mjs", "build:next": "npm run compile && next build --turbopack", "build:blogs": "bundle exec jekyll build --config blogs/_config.yml --source blogs --destination out/blogs --baseurl \"${JEKYLL_BASE_PATH:-/blogs}\"", "build:sitemap": "node scripts/generate-sitemap.mjs", diff --git a/scripts/publish-installer.mjs b/scripts/publish-installer.mjs new file mode 100644 index 0000000..e78dee6 --- /dev/null +++ b/scripts/publish-installer.mjs @@ -0,0 +1,32 @@ +import { createHash } from 'node:crypto'; +import { mkdir, writeFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; +import { pathToFileURL } from 'node:url'; + +export const installerSource = { + revision: '2b0321c94daebdd6a5327b641069af0d0036997b', + sha256: 'b42077317d3eed19ac6090b757c59ce20b2f6a07b912cd4e134fd4cef7e3da0c', +}; + +export async function publishInstaller(outputDirectory = 'out') { + if (!/^[a-f0-9]{40}$/.test(installerSource.revision) || + !/^[a-f0-9]{64}$/.test(installerSource.sha256)) { + throw new Error('Installer source must have a pinned commit and SHA256'); + } + const url = `https://raw.githubusercontent.com/documentdb/documentdb/${installerSource.revision}/packaging/install.sh`; + const response = await fetch(url, { signal: AbortSignal.timeout(60_000) }); + if (!response.ok) { + throw new Error(`Installer download failed: HTTP ${response.status}`); + } + const script = Buffer.from(await response.arrayBuffer()); + const digest = createHash('sha256').update(script).digest('hex'); + if (digest !== installerSource.sha256) { + throw new Error(`Installer checksum mismatch: ${digest}`); + } + await mkdir(outputDirectory, { recursive: true }); + await writeFile(resolve(outputDirectory, 'install.sh'), script); +} + +if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) { + await publishInstaller(); +} diff --git a/tests/packageArticles.test.ts b/tests/packageArticles.test.ts index ab886d9..1767105 100644 --- a/tests/packageArticles.test.ts +++ b/tests/packageArticles.test.ts @@ -13,6 +13,7 @@ import { buildRpmInstallCommand, buildSetupCommand, } from '../app/lib/packageInstall'; +import { FALLBACK_RELEASE } from '../app/lib/releaseInfo'; function getCodeBlocks(content: string, language: string): string[] { const pattern = new RegExp('```' + language + '\\n([\\s\\S]*?)\\n```', 'g'); @@ -273,6 +274,23 @@ describe('Linux package articles', () => { ); }); + it('keeps package download and source links on the mirrored release', () => { + const tags = [ + ...`${linuxPackagesGuideContent}\n${linuxPackagesOperationsContent}`.matchAll(/documentdb\/(?:releases\/download|blob)\/(v[^/]+)\//g), + ].map((match) => match[1]); + + expect(tags.length).toBeGreaterThan(0); + expect(new Set(tags)).toEqual(new Set([FALLBACK_RELEASE.tagName])); + }); + + it('keeps stable installation as the default and makes RC1 an explicit selection', () => { + expect(linuxPackagesGuideContent).toContain('https://documentdb.io/install.sh'); + expect(linuxPackagesGuideContent).toContain('sh documentdb-install.sh --version v1.0-RC1'); + expect(linuxPackagesGuideContent).toContain('defaults to the stable repository, currently v0.117-0'); + expect(linuxPackagesGuideContent).toContain('refuses existing DocumentDB'); + expect(linuxPackagesGuideContent).toContain('RC1 has no maintenance or supported upgrades'); + }); + it('uses the current release package guide and artifact version', () => { const offlineGuide = getArticleByPath('linux-packages', ['offline']); diff --git a/tests/publishInstaller.test.ts b/tests/publishInstaller.test.ts new file mode 100644 index 0000000..82cb32a --- /dev/null +++ b/tests/publishInstaller.test.ts @@ -0,0 +1,65 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { createHash } from 'node:crypto'; +import { mkdtemp, readFile, rm } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import { installerSource, publishInstaller } from '../scripts/publish-installer.mjs'; + +const originalSource = { ...installerSource }; +const directories: string[] = []; +const script = '#!/bin/sh\nprintf "installer fixture\\n"\n'; + +async function outputDirectory() { + const directory = await mkdtemp(path.join(tmpdir(), 'documentdb-installer-')); + directories.push(directory); + return directory; +} + +afterEach(async () => { + Object.assign(installerSource, originalSource); + vi.unstubAllGlobals(); + await Promise.all(directories.splice(0).map((directory) => rm(directory, { recursive: true }))); +}); + +describe('published installer', () => { + it('pins the engine revision and checksum independently of the stable package channel', () => { + expect(installerSource.revision).toMatch(/^[a-f0-9]{40}$/); + expect(installerSource.sha256).toMatch(/^[a-f0-9]{64}$/); + }); + + it('publishes the verified bytes without changing the execution barrier', async () => { + installerSource.revision = 'a'.repeat(40); + installerSource.sha256 = createHash('sha256').update(script).digest('hex'); + const download = vi.fn().mockResolvedValue(new Response(script)); + vi.stubGlobal('fetch', download); + const directory = await outputDirectory(); + await publishInstaller(directory); + expect(await readFile(path.join(directory, 'install.sh'), 'utf8')).toBe(script); + expect(download.mock.calls[0][0]).toBe( + `https://raw.githubusercontent.com/documentdb/documentdb/${'a'.repeat(40)}/packaging/install.sh`, + ); + }); + + it.each(['HTTP error', 'checksum mismatch', 'network error'])('refuses %s without publishing', async (failure) => { + installerSource.revision = 'a'.repeat(40); + installerSource.sha256 = '0'.repeat(64); + const download = vi.fn(); + if (failure === 'network error') { + download.mockRejectedValue(new Error('network error')); + } else { + download.mockResolvedValue(new Response(script, { status: failure === 'HTTP error' ? 404 : 200 })); + } + vi.stubGlobal('fetch', download); + const directory = await outputDirectory(); + await expect(publishInstaller(directory)).rejects.toThrow(); + await expect(readFile(path.join(directory, 'install.sh'))).rejects.toThrow(); + }); + + it('rejects a moving source reference before downloading', async () => { + installerSource.revision = 'main'; + const download = vi.fn(); + vi.stubGlobal('fetch', download); + await expect(publishInstaller(await outputDirectory())).rejects.toThrow('pinned commit'); + expect(download).not.toHaveBeenCalled(); + }); +});