Skip to content

Latest commit

 

History

History
136 lines (105 loc) · 5.94 KB

deployment.md

File metadata and controls

136 lines (105 loc) · 5.94 KB

Deployment

This guide covers building and running the OpenSyria website in an environment that you control. Operations for the hosted service are documented privately.

Runtime Requirements

  • Node.js 24+ and pnpm 11+ for a source build, or Docker for a container build.
  • An API origin that serves the dataset catalog and released dataset metadata.
  • Your own site URL and optional analytics configuration.

Build Configuration

Copy .env.example to .env.local for local development. Configure these values for your own deployment:

Variable Purpose
NEXT_PUBLIC_SITE_URL Canonical public URL of your website
NEXT_PUBLIC_DATASETS_API_URL API origin used for dataset metadata
NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID Optional Google tag or Tag Manager ID
DEPLOYMENT_VERSION Release identifier for health checks and deployment skew protection

The public URL and analytics values are build-time inputs. Rebuild the image when they change. Never put credentials in NEXT_PUBLIC_* variables.

Source Build

From the repository root:

pnpm install --frozen-lockfile
pnpm check
pnpm typecheck
pnpm build
pnpm start

Docker

Build the standalone runtime image with your own public configuration:

docker build --target runtime \
  --build-arg NEXT_PUBLIC_SITE_URL=https://website.example.org \
  --build-arg NEXT_PUBLIC_DATASETS_API_URL=https://api.example.org \
  -t opensyria/website .

Run it locally:

docker run --rm -p 127.0.0.1:3000:3000 opensyria/website

Open http://localhost:3000. Set up HTTPS and routing appropriate to your hosting environment before exposing a deployment publicly.

Verification

GET /health reports application health and the non-secret release identifier. Check the homepage, localized routes, dataset pages, and discovery metadata after a rollout. The public discovery checks can target your own deployment:

AGENT_READINESS_BASE_URL=https://website.example.org pnpm run test:agent-readiness

Dataset pages cache API metadata and may be prerendered into the image. After a dataset release, rebuild the website or allow the configured cache lifetime to expire when a short delay is acceptable.

SEO verification covers clean sitemap URLs and parameterized page responses. Keep query variants separate in any upstream HTML cache: filtered pages carry X-Robots-Tag: noindex, follow, while clean pages must remain indexable. Preserve this response header through the hosting layer. After deployment, verify both admissions languages and the sitemap, then submit the sitemap and inspect the clean/filtered URLs in Google Search Console. See the README's search-indexing policy for tracking redirects and the internal _rsc exception.

Hosted Service Operations

Hosted service deployment tooling and operator procedures are maintained privately. Use the source and Docker instructions above for your own hosting environment. See releases.md for repository release metadata and image identifiers.

Documentation Policy

Public documentation covers application behavior, public endpoints, local setup, and reusable hosting requirements. Keep host inventories, private addresses, account identifiers, access rules, credential arrangements, relationships with unrelated projects, and recovery records in private operator documentation outside public repositories.

Optional Exam Lookup

The site works with manual mark entry when EXAM_RESULTS_ENABLED is unset or false. Enabling import requires Node.js 24 with node:sqlite, a private decoder key, a Turnstile widget restricted to your site, and runtime variables from .env.example. These values are not build arguments or NEXT_PUBLIC_* secrets.

  • EXAM_RESULTS_SITE_ORIGIN: exact HTTPS browser origin (localhost is supported for development).
  • EXAM_RESULTS_TURNSTILE_SITE_KEY and EXAM_RESULTS_TURNSTILE_SECRET: widget credentials. Only the site key is returned to the browser.
  • EXAM_RESULTS_SIGNING_KEY and EXAM_RESULTS_DECODER_KEY: distinct, Base64 encoded 32-byte keys, supplied through private runtime configuration.
  • EXAM_RESULTS_STORE_PATH: writable SQLite file on a protected local volume. Every website deployment slot must mount the same directory and use the same signing key. The directory must also permit SQLite WAL/SHM files. Do not use ephemeral container storage, a network filesystem, or separate per-slot copies.
  • EXAM_RESULTS_TRUST_PROXY: enable only behind a trusted Cloudflare ingress that supplies CF-Connecting-IP and cannot be reached directly by untrusted clients. Without a trusted client address, public lookup fails closed.
  • EXAM_RESULTS_METADATA_PATH: optional read-only JSON snapshot of the public exam governorate/curriculum lists for networks that cannot fetch provider metadata. The server validates the fixed exam year and complete governorate coverage before accepting it. Mount it in every slot, review/refresh it when the provider changes its lists, and restart the app after replacing it. Keep snapshot collection procedures and provider-specific payloads private.

The current store supports replicas on one host only. Use a shared transactional store before distributing application instances across hosts. Endpoint responses are private/no-store; never cache /api/admissions/results, log request bodies, or capture student numbers in analytics. Preserve the browser Origin and client address at ingress. Allow the Turnstile script/frame at https://challenges.cloudflare.com and the configured provider's browser fetches in your Content Security Policy. Cookie, challenge and ticket failures must not fall back to an unrestricted decoding or server-fetch endpoint.

Production checks should cover the real widget, expected hostname/action, manual-entry fallback, both languages, narrow-screen dialogs, private response headers and an explicitly authorized result. Automated tests use synthetic records and injected test services, with no production challenge bypass.