diff --git a/config/local.example.spanner.toml b/config/local.example.spanner.toml new file mode 100644 index 0000000000..34ab6488c8 --- /dev/null +++ b/config/local.example.spanner.toml @@ -0,0 +1,101 @@ +# ============================================================================= +# Default out-of-the-box configuration: Spanner build +# ============================================================================= +# +# This is a ready-to-edit template for running syncstorage-rs against the +# standard Spanner build (`make run_spanner`, the `syncstorage-rs-spanner` +# Docker image). This mirrors the production topology: Syncstorage runs against +# Google Cloud Spanner, while Tokenserver runs against MySQL. +# +# Copy it to `config/local.toml` and edit the values marked REQUIRED: +# +# cp config/local.example.spanner.toml config/local.toml +# +# Every setting can also be supplied as an environment variable prefixed with +# `SYNC_` (nested keys use `__`), e.g. `syncstorage.database_url` becomes +# `SYNC_SYNCSTORAGE__DATABASE_URL`. Environment variables take precedence over +# the config file. See the full reference at docs/src/config.md, and the +# annotated `Settings` structs in `*-settings/src/lib.rs`. +# +# To run against the local Spanner emulator instead of real Spanner, set +# `syncstorage.spanner_emulator_host` (see below) and point the +# `GOOGLE_APPLICATION_CREDENTIALS` env var at a service-account key for real +# Spanner only. +# +# For the all-MySQL build, see `config/local.example.toml`. +# ============================================================================= + +# REQUIRED. Secret used to derive the Hawk signing/token secrets. Use a long, +# random string in any real deployment. No default. +master_secret = "INSERT_SECRET_KEY_HERE" + +# Host/port the server binds to. Defaults: host = "127.0.0.1", port = 8000. +# Use "0.0.0.0" inside containers so the port is reachable from the host. +host = "127.0.0.1" +port = 8000 + +# Emit human-readable logs instead of mozlog JSON. Default: false (0). +human_logs = 1 + +# ----------------------------------------------------------------------------- +# Syncstorage (BSO storage service) — Spanner +# ----------------------------------------------------------------------------- + +# Enable the Syncstorage service. Default: true. +syncstorage.enabled = true + +# REQUIRED when syncstorage is enabled. Spanner DSN, of the form +# spanner://projects//instances//databases/. +# No usable default; the server fails fast at startup if it is unset. A +# "spanner://" scheme is what switches the syncstorage backend into Spanner +# mode (see `Settings::uses_spanner`). +syncstorage.database_url = "spanner://projects/SAMPLE_GCP_PROJECT/instances/SAMPLE_SPANNER_INSTANCE/databases/SAMPLE_SPANNER_DB" + +# Local Spanner emulator host. Leave unset for real Spanner. When running the +# emulator (e.g. via docker/docker-compose.spanner.yaml) set this to the +# emulator's gRPC address so the client talks to the emulator instead of GCP. +# syncstorage.spanner_emulator_host = "localhost:9010" + +# Max size of the syncstorage DB connection pool. Default: 10. +# syncstorage.database_pool_max_size = 10 + +# Quota tracking/enforcement (Spanner-only). Both default to false. Enable +# tracking to record per-user usage; additionally enable enforcement to reject +# writes over `limits.max_quota_limit` (default 2 GB). +syncstorage.enable_quota = 0 +# syncstorage.enforce_quota = 0 + +# ----------------------------------------------------------------------------- +# Tokenserver (node assignment + FxA OAuth verification) — MySQL +# ----------------------------------------------------------------------------- + +# Enable Tokenserver. Default: false. +tokenserver.enabled = true + +# REQUIRED when tokenserver is enabled. In the production topology Tokenserver +# uses MySQL even when Syncstorage uses Spanner. No usable default. +tokenserver.database_url = "mysql://sample_user:sample_password@localhost/tokenserver_rs" + +# Backend type reported in the token response (telemetry only). For the Spanner +# topology leave this as the default "spanner". Valid: "mysql"/"postgres"/"spanner". +tokenserver.node_type = "spanner" + +# Run the tokenserver DB migrations on startup. Default: false. +# tokenserver.run_migrations = true + +# FxA environment used to verify OAuth tokens. The defaults below point at the +# FxA *stage* environment; point them at production for a real deployment +# (fxa_email_domain = "api.accounts.firefox.com", +# fxa_oauth_server_url = "https://oauth.accounts.firefox.com"). +tokenserver.fxa_email_domain = "api-accounts.stage.mozaws.net" +tokenserver.fxa_oauth_server_url = "https://oauth.stage.mozaws.net" + +# REQUIRED for real deployments. Secret used to hash users' metrics UIDs so +# they stay anonymous. Default: "secret" (fine for local dev only). +tokenserver.fxa_metrics_hash_secret = "INSERT_SECRET_KEY_HERE" + +# ----------------------------------------------------------------------------- +# CORS (optional; sensible defaults applied when unset) +# ----------------------------------------------------------------------------- +# cors_allowed_origin = "localhost" # Default: "*" +# cors_max_age = 86400 # Default: 1728000 (20 days) diff --git a/config/local.example.toml b/config/local.example.toml index 6a19cf5c1e..2318a6d2bb 100644 --- a/config/local.example.toml +++ b/config/local.example.toml @@ -1,31 +1,95 @@ +# ============================================================================= +# Default out-of-the-box configuration: MySQL build +# ============================================================================= +# +# This is a ready-to-edit template for running syncstorage-rs against the +# standard MySQL build (the default `cargo build` / `make run_mysql` target, +# and the `syncstorage-rs-mysql` Docker image). It runs both Syncstorage and +# Tokenserver against MySQL. +# +# Copy it to `config/local.toml` and edit the values marked REQUIRED: +# +# cp config/local.example.toml config/local.toml +# +# Every setting can also be supplied as an environment variable prefixed with +# `SYNC_` (nested keys use `__`), e.g. `syncstorage.database_url` becomes +# `SYNC_SYNCSTORAGE__DATABASE_URL`. Environment variables take precedence over +# the config file. See the full reference at docs/src/config.md, and the +# annotated `Settings` structs in `*-settings/src/lib.rs`. +# +# For the Spanner build, see `config/local.example.spanner.toml`. +# ============================================================================= + +# REQUIRED. Secret used to derive the Hawk signing/token secrets. Use a long, +# random string in any real deployment. No default; the server cannot +# authenticate requests without it. master_secret = "INSERT_SECRET_KEY_HERE" -# removing this line will default to moz_json formatted logs (which is preferred for production envs) +# Host/port the server binds to. Defaults: host = "127.0.0.1", port = 8000. +# Use "0.0.0.0" inside containers so the port is reachable from the host. +host = "127.0.0.1" +port = 8000 + +# Emit human-readable logs instead of mozlog JSON. Default: false (0). +# Leave JSON logging on for production; it is the format our tooling expects. human_logs = 1 -# Example Syncstorage settings: -# database_url is required when syncstorage is enabled; there is no default, -# and the server fails to start if it is unset. -# Example MySQL DSN: +# ----------------------------------------------------------------------------- +# Syncstorage (BSO storage service) +# ----------------------------------------------------------------------------- + +# Enable the Syncstorage service. Default: true. +syncstorage.enabled = true + +# REQUIRED when syncstorage is enabled. MySQL DSN for the syncstorage database. +# There is no usable default: the server fails fast at startup if it is unset, +# rather than silently connecting to a default host. syncstorage.database_url = "mysql://sample_user:sample_password@localhost/syncstorage_rs" -# Example Spanner DSN: -# syncstorage.database_url="spanner://projects/SAMPLE_GCP_PROJECT/instances/SAMPLE_SPANNER_INSTANCE/databases/SAMPLE_SPANNER_DB" -# enable quota limits + +# Max size of the syncstorage DB connection pool. Default: 10. +# syncstorage.database_pool_max_size = 10 + +# Quota tracking/enforcement are Spanner-only features and are force-disabled +# for non-Spanner backends, so they have no effect on the MySQL build. syncstorage.enable_quota = 0 -# set the quota limit to 2GB. -# max_quota_limit = 200000000 -syncstorage.enabled = true + +# Request/batch limits. Defaults shown in docs/src/config.md (Syncstorage +# Limits). `max_total_records` is also used to cap MySQL `get_bsos` result +# sizes; see issues #298/#333. syncstorage.limits.max_total_records = 9984 -# Example Tokenserver settings: -# database_url is required when tokenserver is enabled; there is no default, -# and the server fails to start if it is unset. -tokenserver.database_url = "mysql://sample_user:sample_password@localhost/tokenserver_rs" +# ----------------------------------------------------------------------------- +# Tokenserver (node assignment + FxA OAuth verification) +# ----------------------------------------------------------------------------- + +# Enable Tokenserver. Default: false. Enable it for a self-contained server +# that both assigns nodes and stores data. tokenserver.enabled = true + +# REQUIRED when tokenserver is enabled. MySQL DSN for the tokenserver database. +# No usable default; the server fails fast at startup if it is unset. +tokenserver.database_url = "mysql://sample_user:sample_password@localhost/tokenserver_rs" + +# Backend type reported in the token response (telemetry only). For the MySQL +# build set this to "mysql". Default: "spanner". Valid: "mysql"/"postgres"/"spanner". +tokenserver.node_type = "mysql" + +# Run the tokenserver DB migrations on startup. Default: false. +# tokenserver.run_migrations = true + +# FxA environment used to verify OAuth tokens. The defaults below point at the +# FxA *stage* environment; point them at production for a real deployment +# (fxa_email_domain = "api.accounts.firefox.com", +# fxa_oauth_server_url = "https://oauth.accounts.firefox.com"). tokenserver.fxa_email_domain = "api-accounts.stage.mozaws.net" -tokenserver.fxa_metrics_hash_secret = "INSERT_SECRET_KEY_HERE" tokenserver.fxa_oauth_server_url = "https://oauth.stage.mozaws.net" -# cors settings -# cors_allowed_origin = "localhost" -# cors_max_age = 86400 +# REQUIRED for real deployments. Secret used to hash users' metrics UIDs so +# they stay anonymous. Default: "secret" (fine for local dev only). +tokenserver.fxa_metrics_hash_secret = "INSERT_SECRET_KEY_HERE" + +# ----------------------------------------------------------------------------- +# CORS (optional; sensible defaults applied when unset) +# ----------------------------------------------------------------------------- +# cors_allowed_origin = "localhost" # Default: "*" +# cors_max_age = 86400 # Default: 1728000 (20 days) diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index a0e830afdd..777f9259db 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -37,6 +37,7 @@ [Mozilla Accounts Server - FxA](mozilla-accounts.md) - [How To Guides](how-to/index.md) + - [Default Configuration for Spanner & MySQL Builds](how-to/default-config.md) - [Run Your Own Sync-1.5 Server with Docker](how-to/how-to-run-with-docker.md) - [Run Your Own Sync-1.5 Server (legacy)](how-to/how-to-run-sync-server.md) - [Configure Sync Server for TLS (legacy)](how-to/how-to-config-tls.md) diff --git a/docs/src/how-to/default-config.md b/docs/src/how-to/default-config.md new file mode 100644 index 0000000000..4d5dde7392 --- /dev/null +++ b/docs/src/how-to/default-config.md @@ -0,0 +1,68 @@ +# Default Configuration for Spanner & MySQL Builds + +This page describes the out-of-the-box configuration needed to run the two +standard `syncstorage-rs` builds: + +- **MySQL build** — the default `cargo build` / `make run_mysql` target and the + `syncstorage-rs-mysql` Docker image. Both Syncstorage and Tokenserver run + against MySQL. +- **Spanner build** — `make run_spanner` and the `syncstorage-rs-spanner` + Docker image. This mirrors production: Syncstorage runs against Google Cloud + Spanner while Tokenserver runs against MySQL. + +Annotated, copy-paste-ready templates live in the repo: + +| Build | Template | +| --- | --- | +| MySQL | [`config/local.example.toml`](https://github.com/mozilla-services/syncstorage-rs/blob/master/config/local.example.toml) | +| Spanner | [`config/local.example.spanner.toml`](https://github.com/mozilla-services/syncstorage-rs/blob/master/config/local.example.spanner.toml) | + +Copy one to `config/local.toml` and edit the values marked `REQUIRED`: + +```sh +cp config/local.example.toml config/local.toml # MySQL +# or +cp config/local.example.spanner.toml config/local.toml # Spanner +``` + +Every setting can also be supplied as an environment variable prefixed with +`SYNC_` (nested keys use `__`). For example `syncstorage.database_url` becomes +`SYNC_SYNCSTORAGE__DATABASE_URL`. Environment variables take precedence over the +config file. The complete list of options and their defaults is in the +[Application Configuration](../config.md) reference, and the source of truth is +the doc-commented `Settings` structs in the `*-settings` crates. + +## Minimum required settings + +Most settings have sensible defaults. Regardless of backend you must supply: + +| Setting | Why | +| --- | --- | +| [`master_secret`](../config.md#SYNC_MASTER_SECRET) | Derives the Hawk signing/token secrets. No default. | +| [`syncstorage.database_url`](../config.md#SYNC_SYNCSTORAGE__DATABASE_URL) | No usable default; the server fails fast at startup if unset. | +| [`tokenserver.database_url`](../config.md#SYNC_TOKENSERVER__DATABASE_URL) | Required when `tokenserver.enabled = true` (fails fast if unset). | + +Backend-specific notes: + +- **MySQL:** set `tokenserver.node_type = "mysql"`. Syncstorage MySQL schema + migrations run automatically at startup; set + `tokenserver.run_migrations = true` to apply the Tokenserver schema too. +- **Spanner:** `syncstorage.database_url` must use the + `spanner://projects/.../instances/.../databases/...` form — the `spanner://` + scheme is what selects the Spanner backend. Leave `tokenserver.node_type` at + its default (`"spanner"`). To run against the local emulator instead of GCP, + set `syncstorage.spanner_emulator_host` (e.g. `localhost:9010`); for real + Spanner, point `GOOGLE_APPLICATION_CREDENTIALS` at a service-account key. + +## Run it out of the box with Docker + +For a zero-to-running MySQL stack (database + server, schema applied +automatically, ready to serve), see the +[one-shot MySQL `docker compose`](how-to-run-with-docker.md#docker-compose-one-shot-with-mysql) +recipe. It brings up MySQL and the server, runs migrations, and bootstraps the +storage node so that `curl http://localhost:8000/__heartbeat__` succeeds with no +further setup. + +A Spanner stack cannot be fully zero-config: real Spanner requires GCP +credentials, and the local emulator requires extra wiring (see +`docker/docker-compose.spanner.yaml` and `make run_spanner`). diff --git a/docs/src/how-to/how-to-run-with-docker.md b/docs/src/how-to/how-to-run-with-docker.md index b335189b7f..833661d8e6 100644 --- a/docs/src/how-to/how-to-run-with-docker.md +++ b/docs/src/how-to/how-to-run-with-docker.md @@ -10,7 +10,7 @@ Images are available for both and [PostgreSQL](https://github.com/mozilla-services/syncstorage-rs/pkgs/container/syncstorage-rs%2Fsyncstorage-rs-postgres) as the database. Differences in configuration or deployment steps will be -noted. +noted. Tagged release builds are available on ghcr.io. To pin to a specific version, set `SYNCSERVER_VERSION` to the desired release tag (e.g., `SYNCSERVER_VERSION=v1.45.0`) @@ -142,6 +142,145 @@ Next, start the service with `docker compose`: docker compose -f docker-compose.one-shot.yaml up -d ``` +## Docker Compose, One-Shot with MySQL + +This recipe brings up everything needed for a working MySQL-backed server in a +single command: a MySQL database for Syncstorage and one for Tokenserver, plus +the server itself. Syncstorage applies its schema migrations automatically at +startup, `SYNC_TOKENSERVER__RUN_MIGRATIONS` applies the Tokenserver schema, and +`SYNC_TOKENSERVER__INIT_NODE_URL` bootstraps the `sync-1.5` service and storage +node records — so the stack is ready to serve with no manual database setup. + +### Option A: build from source (works from a checkout) + +Run this from a clone of the repository; the build `context` is the repo root, +so the MySQL build of the server is compiled locally and the recipe does not +depend on any published image. Save the yaml below into a file, e.g. +`docker-compose.one-shot.yaml`. + +```yaml +services: + syncserver: + build: + context: . + args: + SYNCSTORAGE_DATABASE_BACKEND: mysql + TOKENSERVER_DATABASE_BACKEND: mysql + container_name: syncserver + ports: + - "8000:8000" + environment: + SYNC_HOST: "0.0.0.0" + SYNC_PORT: "8000" + SYNC_MASTER_SECRET: "${SYNC_MASTER_SECRET:-changeme_secret_key}" + SYNC_SYNCSTORAGE__DATABASE_URL: "mysql://sync:sync@sync-db:3306/syncstorage" + SYNC_TOKENSERVER__DATABASE_URL: "mysql://sync:sync@tokenserver-db:3306/tokenserver" + SYNC_TOKENSERVER__ENABLED: "true" + SYNC_TOKENSERVER__NODE_TYPE: "mysql" + SYNC_TOKENSERVER__RUN_MIGRATIONS: "true" + SYNC_TOKENSERVER__FXA_EMAIL_DOMAIN: "api.accounts.firefox.com" + SYNC_TOKENSERVER__FXA_OAUTH_SERVER_URL: "https://oauth.accounts.firefox.com" + SYNC_HUMAN_LOGS: "${SYNC_HUMAN_LOGS:-false}" + RUST_LOG: "${RUST_LOG:-info}" + SYNC_TOKENSERVER__INIT_NODE_URL: "${SYNC_TOKENSERVER__INIT_NODE_URL:-http://localhost:${SYNC_PORT:-8000}}" + depends_on: + sync-db: + condition: service_healthy + tokenserver-db: + condition: service_healthy + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8000/__heartbeat__"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s + + sync-db: + image: docker.io/library/mysql:8.0 + container_name: syncserver-sync-db + command: --explicit_defaults_for_timestamp + environment: + MYSQL_RANDOM_ROOT_PASSWORD: "yes" + MYSQL_DATABASE: syncstorage + MYSQL_USER: sync + MYSQL_PASSWORD: sync + volumes: + - sync_db_data:/var/lib/mysql + healthcheck: + test: ["CMD-SHELL", "mysqladmin -h 127.0.0.1 -usync -psync ping"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + restart: unless-stopped + + tokenserver-db: + image: docker.io/library/mysql:8.0 + container_name: syncserver-tokenserver-db + command: --explicit_defaults_for_timestamp + environment: + MYSQL_RANDOM_ROOT_PASSWORD: "yes" + MYSQL_DATABASE: tokenserver + MYSQL_USER: sync + MYSQL_PASSWORD: sync + volumes: + - tokenserver_db_data:/var/lib/mysql + healthcheck: + test: ["CMD-SHELL", "mysqladmin -h 127.0.0.1 -usync -psync ping"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + restart: unless-stopped + +volumes: + sync_db_data: + driver: local + tokenserver_db_data: + driver: local +``` + +Next, build and start the service with `docker compose`: + +```sh +docker compose -f docker-compose.one-shot.yaml up -d --build +``` + +Once the `syncserver` container reports healthy, confirm it is serving: + +```sh +curl http://localhost:8000/__heartbeat__ +``` + +### Option B: use a published image + +Mozilla also publishes prebuilt images on ghcr.io. Note that these are +currently tagged by commit SHA — there is **no `latest` or semver tag** — so +you must pin `SYNCSERVER_VERSION` to a tag listed on the +[`syncstorage-rs-mysql` packages page](https://github.com/mozilla-services/syncstorage-rs/pkgs/container/syncstorage-rs%2Fsyncstorage-rs-mysql). +To use a published image, replace the `syncserver` service's `build:` block with +an `image:` reference; the `sync-db`, `tokenserver-db`, and `volumes` sections +are unchanged: + +```yaml +services: + syncserver: + image: ghcr.io/mozilla-services/syncstorage-rs/syncstorage-rs-mysql:${SYNCSERVER_VERSION:?set SYNCSERVER_VERSION to a published tag} + platform: linux/amd64 + container_name: syncserver + # ...the remaining syncserver settings are identical to Option A +``` + +Then start it with the tag pinned (published images are `linux/amd64`): + +```sh +SYNCSERVER_VERSION= docker compose -f docker-compose.one-shot.yaml up -d +``` + +> Set `SYNC_MASTER_SECRET` to your own value for anything beyond local +> experimentation; the default above is a placeholder. + ## Configuring Firefox (Desktop) Firefox itself needs to be configured to use the self-hosted Sync server. diff --git a/docs/src/how-to/index.md b/docs/src/how-to/index.md index 07de25c9a8..c35186c16d 100644 --- a/docs/src/how-to/index.md +++ b/docs/src/how-to/index.md @@ -2,6 +2,7 @@ Collection of How To guides for various Sync-related operations. +- [Default Configuration for Spanner & MySQL Builds](default-config.md) - [Run Your Own Sync-1.5 Server with Docker](how-to-run-with-docker.md) - [Run Your Own Sync-1.5 Server (legacy)](how-to-run-sync-server.md) - [Configure Sync Server for TLS (legacy)](how-to-config-tls.md) diff --git a/syncserver-settings/src/lib.rs b/syncserver-settings/src/lib.rs index bbdf2ebd5c..1feb487c11 100644 --- a/syncserver-settings/src/lib.rs +++ b/syncserver-settings/src/lib.rs @@ -16,7 +16,10 @@ static PREFIX: &str = "sync"; #[derive(Clone, Debug, Deserialize)] #[serde(default)] pub struct Settings { + /// TCP port the server binds to. Default: 8000. pub port: u16, + /// Host address the server binds to. Default: "127.0.0.1". Use "0.0.0.0" + /// inside containers so the port is reachable from the host. pub host: String, /// Keep-alive header value (seconds) pub actix_keep_alive: Option, @@ -25,9 +28,13 @@ pub struct Settings { /// that are used during Hawk authentication. pub master_secret: Secrets, + /// Emit human-readable logs instead of mozlog JSON. Default: false. + /// Production environments should leave this off (JSON is preferred). pub human_logs: bool, + /// Hostname of the StatsD/metrics sink. Default: "localhost". pub statsd_host: Option, + /// Port of the StatsD/metrics sink. Default: 8125. pub statsd_port: u16, /// Whether to include the hostname in metrics, which increases cardinality significantly in /// prod. diff --git a/syncstorage-settings/src/lib.rs b/syncstorage-settings/src/lib.rs index b1680291f0..e19db35c21 100644 --- a/syncstorage-settings/src/lib.rs +++ b/syncstorage-settings/src/lib.rs @@ -75,6 +75,7 @@ pub struct Settings { /// fails fast at startup (see `syncserver_settings::Settings::validate`) /// rather than silently connecting to a default host. pub database_url: String, + /// Max size of the database connection pool. Default: 10. pub database_pool_max_size: u32, /// Pool timeout when waiting for a slot to become available, in seconds pub database_pool_connection_timeout: Option, @@ -92,15 +93,24 @@ pub struct Settings { /// Server-enforced limits for request payloads. pub limits: ServerLimits, + /// StatsD metrics label prefix for syncstorage. Default: "syncstorage". pub statsd_label: String, + /// Track per-user storage quota. Spanner-only; force-disabled on other + /// backends. Default: false. pub enable_quota: bool, + /// Reject writes that exceed `limits.max_quota_limit`. Requires + /// `enable_quota`. Spanner-only; force-disabled on other backends. + /// Default: false. pub enforce_quota: bool, /// Whether Glean telemetry metric emission is enabled. pub glean_enabled: bool, + /// gRPC address of a local Spanner emulator (e.g. "localhost:9010"). + /// Leave unset to use real Spanner. Default: None. pub spanner_emulator_host: Option, + /// Whether the Syncstorage service is enabled. Default: true. pub enabled: bool, /// Fail the `/__lbheartbeat__` healthcheck after running for this duration