diff --git a/docs/reference/docker.md b/docs/reference/docker.md index 9f817547..8fd59fdb 100644 --- a/docs/reference/docker.md +++ b/docs/reference/docker.md @@ -1,289 +1,129 @@ --- sidebar_position: 3 +title: Docker reference --- -# Docker +# Docker reference -## Docker Container Images +Resgrid Core is published as Docker images and configured entirely with environment variables. The [self-hosted deployments](../self-hosted/installation) set everything up for you; this page is the reference behind them. -Resgrid is split into 4 distinct Docker containers. All of our container images are available under the Resgrid, LLC organization on the Docker Hub. +## Images -**resgridwebcore** This is the web application docker image and is used to host the website application that users will interact with. +All images are on Docker Hub under `resgridllc`, tagged `latest` and with a build version (`4..0`). They run as a non-root user, listen on port **8080**, contain no shell, and start through a small `wait` program that waits for the hosts in `WAIT_HOSTS` (for example `db:5432,redis:6379,rabbitmq:5672`) before starting. -Docker Pull Command: +| Image | Service | Health endpoint | +|---|---|---| +| `resgridllc/resgridwebcore` | Web application | `/Health/GetCurrent` | +| `resgridllc/resgridwebservices` | REST API and OpenID Connect token server | `/health`, `/api/v4/Health/GetCurrent` | +| `resgridllc/resgridwebevents` | Real-time events (SignalR hubs `/eventingHub`, `/geolocationHub`, `/chatHub`) | `/health/GetCurrent` | +| `resgridllc/resgridworkersconsole` | Background jobs and database migrations | — | +| `resgridllc/resgridwebtts` | Text-to-speech (Piper) | `/health` | +| `resgridllc/resgridwebmcp` | Model Context Protocol server (`POST /mcp`) | `/health/current` | +| `resgridllc/resgridtrackergateway` | GPS tracker ingest (TCP/UDP 5004, 5023, 5027) | `/health/live`, `/health/ready` | +| `resgridllc/resgridrelay` | Inbound email to dispatch (SMTP 2525) | — | +| `resgridllc/resgridwebbroker` | Protected-data broker for the Advanced Data Protection add-on; not used by self-hosted installs | `/health` | -```bash -docker pull resgridllc/resgridwebcore -``` - -**resgridwebservices** This is the web api that is used by the website and applications to communicate with the Resgrid system - -Docker Pull Command: - -```bash -docker pull resgridllc/resgridwebservices -``` +The images are `linux/amd64` today. Supporting services: PostgreSQL 18 (all databases), Redis, RabbitMQ 4 (with `deprecated_features.permit.transient_nonexcl_queues = true`) and any S3-compatible object store (RustFS in the reference deployments). MongoDB and SQL Server are no longer required. -**resgridworkersconsole** This is the backend workers that are used to process operations from RabbitMQ or scheduled tasks. +## How settings are read -Docker Pull Command: +Every static field of every class in [Core/Resgrid.Config](https://github.com/Resgrid/Core/tree/master/Core/Resgrid.Config) can be set with an environment variable named: -```bash -docker pull resgridllc/resgridworkersconsole +```text +RESGRID__{ClassName}__{FieldName} ``` -**resgridwebevents** This is the web api that houses the SignalR hubs used for live updating applications based on events. - -Docker Pull Command: +- `RESGRID` in capitals, then two underscores between the parts: `RESGRID__DataConfig__ConnectionString`. +- Class and field names are **case-sensitive**. A misspelled name is **silently ignored** (the container logs the value it applied at start-up, `Resgrid.Config: Setting Value for ...`). +- Enum values must be **numbers** (`RESGRID__SystemBehaviorConfig__Environment=0`). +- Lists and dictionaries can't be set this way. +- A few field names contain spelling mistakes that must be kept: `ExternalAudioUrlParamPasshprase`, `RabbbitPassword`, `RabbbitExchange`. + +Two settings have only two parts and are read by the worker: `RESGRID__DODBUPGRADE=true` (create and migrate the database schema at start-up) and `RESGRID__EVENTSONLY`. + +## Required settings + +The complete, commented template is [`.env.example`](https://github.com/Resgrid/resgrid-setup/blob/master/.env.example) in resgrid-setup. The settings every install needs: + +| Setting | Value | +|---|---| +| `RESGRID__SystemBehaviorConfig__ResgridBaseUrl` / `ResgridApiBaseUrl` / `ResgridEventingBaseUrl` | Public HTTPS URLs of the web app, API and events hub. The apps learn the events URL from the API. | +| `RESGRID__SystemBehaviorConfig__Environment` | `0` Production, `1` Staging, `2` QA, `3` Development | +| `RESGRID__SystemBehaviorConfig__ExternalLinkUrlParamPassphrase`, `ExternalAudioUrlParamPasshprase`, `ApiTokenEncryptionPassphrase` | Random secrets | +| `RESGRID__SecurityConfig__EncryptionKey` | Random, **exactly 32 characters** | +| `RESGRID__SecurityConfig__EncryptionSaltValue` | Random | +| `RESGRID__ApiConfig__BackendInternalApikey` | Random; the web app uses it to get API tokens for chat, messages, maps and the events connection | +| `RESGRID__ApiConfig__BypassSslChecks` | `false`. With `true` the web app, API and events refuse to start in Production; make the containers trust your CA instead (`SSL_CERT_DIR`/`SSL_CERT_FILE`). | +| `RESGRID__DataConfig__DatabaseType` / `DocDatabaseType` | `1` (PostgreSQL) | +| `RESGRID__DataConfig__ConnectionString` and `CoreConnectionString` | Same value: `User ID=…;Password=…;Host=db;Port=5432;Database=resgrid;Pooling=true;` (`CoreConnectionString` is used by the repositories and the migration runner) | +| `RESGRID__DataConfig__DocumentConnectionString` | `…;Database=resgriddoc;` | +| `RESGRID__OidcConfig__DatabaseType` / `ConnectionString` | `1` / `…;Database=resgridoidc;` | +| `RESGRID__OidcConfig__EncryptionCert` / `SigningCert` | Base64 PKCS#12 certificates **with an empty password** (key encipherment / digital signature) | +| `RESGRID__WorkerConfig__DatabaseType` / `WorkerDbConnectionString` / `PayloadKey` | `1` / `…;Database=resgridworkers;` / random | +| `RESGRID__JwtConfig__EventsClientSecret` | Random; same value for the API and events containers | +| `RESGRID__CacheConfig__RedisConnectionString` | `redis:6379,password=…,allowAdmin=True` | +| `RESGRID__ServiceBusConfig__RabbitHostname` / `RabbitUsername` / `RabbbitPassword` | RabbitMQ host and credentials | +| `RESGRID__WebConfig__IngressProxyNetwork` / `IngressProxyNetworkCidr` | Network your reverse proxy connects from; forwarded headers are only trusted from it | +| `RESGRID__TtsConfig__ServiceBaseUrl`, `StaticPromptAdminKey`, `S3Endpoint`, `S3AccessKey`, `S3SecretKey`, `S3Bucket` | Text-to-speech service URL, admin key and its bucket | +| `RESGRID__SystemBehaviorConfig__AllowSignupWithoutBilling` | `true` to allow **Sign Up** on a self-hosted server (no billing service); set to `false` once your department exists | +| `ASPNETCORE_ENVIRONMENT` / `ASPNETCORE_URLS` | `Production` / `http://+:8080` | + +Generate the OpenID Connect certificates with OpenSSL: ```bash -docker pull resgridllc/resgridwebevents +openssl req -x509 -newkey rsa:2048 -sha256 -days 3650 -nodes -keyout enc.key -out enc.crt \ + -subj "/CN=Resgrid OIDC Server Encryption Certificate" -addext "keyUsage=keyEncipherment" +openssl pkcs12 -export -out enc.pfx -inkey enc.key -in enc.crt -passout pass: +openssl base64 -A -in enc.pfx # -> RESGRID__OidcConfig__EncryptionCert ``` -## Settings - -To configure the Resgrid system in a Docker or Kubernetes context we recommend using environment variables. To see all the config options available you can take a look at our Github repo , every static class in the Resgrid.Config project can be set by an environment variable. - -The pattern for how Resgrid processes environment is as follows: - -``` -RESGRID__{CLASSNAME}__{PROPERTYNAME} +Repeat with `keyUsage=digitalSignature` for `RESGRID__OidcConfig__SigningCert`. Replacing either certificate later signs every user out; changing the encryption key or salt makes encrypted data unreadable. + +## Common optional settings + +| Setting | Purpose | +|---|---| +| `RESGRID__SystemBehaviorConfig__OutboundEmailType` | `1` SMTP, `0` Postmark | +| `RESGRID__OutboundEmailServerConfig__Host`, `Port`, `EnableSsl`, `UserName`, `Password`, `FromMail`, `PostmarkApiKey` | Outbound email | +| `RESGRID__SystemBehaviorConfig__ErrorLoggerType` | `2` console, `1` Sentry (`RESGRID__ExternalErrorConfig__ExternalErrorServiceUrlForApi`, `ForWebsite`, `ForWebjobs`, `ForEventing`) | +| `RESGRID__MappingConfig__LeafletTileUrl`, `LeafletAttribution` | Web map tiles (`{z}/{x}/{y}` URL; `{0}` is replaced by `WebsiteOSMKey` if set) | +| `RESGRID__MappingConfig__NominatimUrl`, `OsrmUrl` | Address search and routing for the web map | +| `RESGRID__MappingConfig__GoogleMapsApiKey`, `GoogleMapsJSKey`, `What3WordsApiKey`, `LocationIQApiKey` | Geocoding providers | +| `RESGRID__MappingConfig__ResponderAppMapboxKey`, `UnitAppMapBoxKey` | Maps in the mobile apps | +| `RESGRID__WebConfig__RecaptchaPublicKey`, `RecaptchaPrivateKey` | reCAPTCHA on Sign Up and Forgot Password; leave empty to disable | +| `RESGRID__ImportConfig__S3Endpoint`, `S3AccessKey`, `S3SecretKey`, `S3Bucket` | Storage for Department Import | +| `RESGRID__SecurityConfig__SystemApiKey` | Server-to-server key (header `X-Resgrid-SystemApiKey`), used by the email relay | +| `RESGRID__SecurityConfig__DepartmentApiKeyPepper` | Pepper for department API keys | +| `RESGRID__TwoFactorConfig__AuthenticatorSeedEncryptionEnabled` | Encrypt authenticator (TOTP) seeds at rest | +| `RESGRID__SystemBehaviorConfig__FullHealthCheckKey` | Key for the detailed `/health/full` endpoints (header `X-Resgrid-Health-Key`) | +| `RESGRID__SearchConfig__Enabled`, `IndexPath`, `S3Endpoint`, `S3AccessKey`, `S3SecretKey`, `S3Bucket`, `S3UseSsl`, `S3ForcePathStyle` | Built-in search index (off by default). `IndexPath` is local scratch space (`/data/search`, a tmpfs or `emptyDir` in resgrid-setup); with the `S3*` settings the index is kept in object storage so every container shares it. resgrid-setup sets these when you turn on the *Unified search* feature. | +| `RESGRID__AttachmentScanningConfig__Enabled`, `Host`, `Port` | Virus scanning of uploaded files through a ClamAV daemon (`clamav:3310`); set by the *Attachment virus scanning* feature | +| `RESGRID__UnitTrackingConfig__Enabled`, `NativeGatewayEnabled`, `EnableQueclink`, `EnableGt06`, `EnableTeltonika`, `CredentialPepper` | GPS tracker gateway | +| `RESGRID__McpConfig__CorsAllowedOrigins` | MCP CORS origins | +| `RESGRID__ChatConfig__Novu*` | Push notifications through a Novu instance | + +## Settings used by resgrid-setup + +These entries in `.env` are read by the setup scripts and Docker Compose, not by Resgrid: + +| Setting | Purpose | +|---|---| +| `RESGRID_WEB_HOST`, `RESGRID_API_HOST`, `RESGRID_EVENTS_HOST`, `RESGRID_TLS`, `RESGRID_HTTP_PORT`, `RESGRID_HTTPS_PORT` | Hostnames, certificate mode and ports for Caddy | +| `RESGRID_FEATURES` | Optional features that are on (section names from `features/catalog.ini`); see [Optional features and upgrades](../self-hosted/features-and-upgrades) | +| `RESGRID_FEATURES_OFFERED` | Optional features setup has asked about; later runs only ask about new ones | +| `COMPOSE_PROFILES` | Optional services to run (`maps`, `clamav`, `tracking`, `relay`); Docker Compose reads it from `.env` itself. `RESGRID_OPTIONAL` plays the same part on Kubernetes. | + +Feature flags themselves live in the `featureflags` table of the `resgrid` database (`isenabledglobally` switches a flag for every department) and are cached in Redis under keys containing `FeatureFlag`. After changing them with SQL, delete those keys (`redis-cli --scan --pattern '*FeatureFlag*'`) or wait for the cache to expire; `./setup.sh features` does both for you. + +## Trusting a private certificate authority + +The containers verify certificates when they call your public URLs (for example the web app's server-side calls to the API). With a private CA, mount its certificate into each container and add the directory to `SSL_CERT_DIR`: + +```yaml +environment: + SSL_CERT_DIR: /etc/ssl/certs:/usr/local/share/resgrid-ca +volumes: + - ./certs/ca:/usr/local/share/resgrid-ca:ro ``` -Resgrid at the start of the name must be in all caps, there are two (2) underscores separating the parts, in between RESGRID and classname and classname and popertyname. - -## Environment Variables - -Resgrid is configured via Environment Variables for the Docker Containers that are run. - -```text title="~/resgrid/resgrid.env" -# ----------------------------------------------- -# ------ Resgrid Environment Variables ---------- -# ----------------------------------------------- - -# DOCUMENTATION FOR THESE VARIABLES IS FOUND UNDER -# https://resgrid-core.readthedocs.io/en/latest/docker/index.html - -# ----------------------------------------------- -# --- System Behavior Config Section ------------ -# ----------------------------------------------- - -# Url to access the Resgrid api (required) -# Note: localhost will only support local access, you must replace localhost (or localhost:5152) with an IP Address or server name for external (non-local) access -RESGRID__SystemBehaviorConfig__ResgridApiBaseUrl=http://localhost:5152 - -# Url to access the Resgrid web interface (required) -# Note: localhost will only support local access, you must replace localhost (or localhost:5151) with an IP Address or server name for external (non-local) access -RESGRID__SystemBehaviorConfig__ResgridBaseUrl=http://localhost:5151 - -# Url to access the Resgrid events site (required) -# Note: localhost will only support local access, you must replace localhost (or localhost:5153) with an IP Address or server name for external (non-local) access -RESGRID__SystemBehaviorConfig__ResgridEventingBaseUrl=http://localhost:5153 - -# Environment system is currently running in -# 0 => Production -# 1 => Staging -# 2 => QA -# 3 => Development -RESGRID__SystemBehaviorConfig__Environment=3 - -# Error logging system use by Resgrid -# 0 => Elk-Elastic (MUST: Configure ELK Config Section) -# 1 => Sentry.io (MUST: ) -RESGRID__SystemBehaviorConfig__ErrorLoggerType=0 - -# Outbound Email Provider -# 0 => Postmark (MUST: Configure Postmark Config Section) -# 1 => Smtp (MUST: Configure Outbound Email Config Section) -RESGRID__SystemBehaviorConfig__OutboundEmailType=1 - -# String passphase to symmetrically encrypt external url values -# Note: Recommended to change for security purposes -RESGRID__SystemBehaviorConfig__ExternalLinkUrlParamPassphrase=NvM28Q8EJejQSdxS - -# String passphase to symmetrically encrypt call audio url -# Note: Recommended to change for security purposes -RESGRID__SystemBehaviorConfig__ExternalAudioUrlParamPasshprase=5a4tALka7bz6h4CY - -# ----------------------------------------------- -# --- Cache Config Section ---------------------- -# ----------------------------------------------- - -# Redis server connection string -RESGRID__CacheConfig__RedisConnectionString=redis:6379,Password=,allowAdmin=True - -# ----------------------------------------------- -# --- Data Config Section ----------------------- -# ----------------------------------------------- - -# Microsoft SQL Server Connection String for main Resgrid Database -RESGRID__DataConfig__ConnectionString=Server=db;Database=Resgrid;User Id=sa;Password=Resgrid123!!;MultipleActiveResultSets=True;TrustServerCertificate=True;Connection Timeout=30; - -# ----------------------------------------------- -# --- Mapping Geolocation Config Section -------- -# ----------------------------------------------- - -# Google Maps JS API Key -# https://developers.google.com/maps/documentation/javascript/get-api-key -RESGRID__MappingConfig__GoogleMapsJSKey= - -# Google Maps Geocoding API Key -# https://developers.google.com/maps/documentation/geocoding/get-api-key -RESGRID__MappingConfig__GoogleMapsApiKey= - -# what 3 words api key -# https://developer.what3words.com/public-api -RESGRID__MappingConfig__What3WordsApiKey= - -# ----------------------------------------------- -# --- Error Config Section ---------------------- -# ----------------------------------------------- - -# String to tag the Environment for error logging -RESGRID__ExternalErrorConfig__Environment=dev - -# ----------------------------------------------- -# --- ELK Config Section ------------------------ -# ----------------------------------------------- - -# Url for Elastic Kilbana error log ingestion service -RESGRID__ExternalErrorConfig__ElkServiceUrl=http://elk:9200 - -# ----------------------------------------------- -# --- Sentry.io Config Section ------------------ -# ----------------------------------------------- - -# NOTE: For Sentry.io, you should create 3 projects, 1 for the website, 1 for the api and 1 for workers (webjobs) -# https://docs.sentry.io/product/sentry-basics/integrate-frontend/create-new-project/ - -# Sentry.io Project Url for Resgrid API System -RESGRID__ExternalErrorConfig__ExternalErrorServiceUrl= - -# Sentry.io Project Url for Resgrid website -RESGRID__ExternalErrorConfig__ExternalErrorServiceUrlForWebsite= - -# Sentry.io Project Url for webjobs -RESGRID__ExternalErrorConfig__ExternalErrorServiceUrlForWebjobs= - -# ----------------------------------------------- -# --- Oidc Config Section ----------------------- -# ----------------------------------------------- - -# Microsoft SQL Server Connection String for Resgrid OpenId Connect database -RESGRID__OidcConfig__ConnectionString=Server=db;Database=ResgridOIDC;User Id=sa;Password=Resgrid123!!;MultipleActiveResultSets=True; - -# OpenId Connect Encryption Certificate -# Note: This is the Encipherment Certificate, for secutiry purposes you MUST generate a new certificate. -# See: https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html#registering-a-certificate-recommended-for-production-ready-scenarios -RESGRID__OidcConfig__EncryptionCert=MIIJagIBAzCCCSYGCSqGSIb3DQEHAaCCCRcEggkTMIIJDzCCBYgGCSqGSIb3DQEHAaCCBXkEggV1MIIFcTCCBW0GCyqGSIb3DQEMCgECoIIE5jCCBOIwHAYKKoZIhvcNAQwBAzAOBAh1MHkmOjAAygICB9AEggTAv3SFfe68h8yWUSJPj3Jxd4Tb638+ssFHX2hKyt0bF1ImoNiAIm6bLLgrZSg/tIot44+wGTHMeSa+tYjy8odQOynT5/w2KHG+Z9J0n8Ryf1EMhaPJdL0cdMMolaTGU5ISldEXgN6fQgGfsCEI0jHBy5q9H2YKqctGTEz1RAT8jui879gfGxcz9xaYvP9Ed8HA53RvUy0sOI/QwJRTpZ85OIC+Y6YvnT1mOtnPXbatQF0ffcEPAub23aU2TNDsXzAj+hk98Zad3u5+FAnxHIFkcNSlABZR85JbbwMG6Iu28BcQ/hj9pNrgD6GRq/bGquspcBYxGPvdKL2GHhRw6WUPzBQgjDVym6wa6TCjQeFfdMcsLQdcMkApUYCc4v8rrgqLy9i0byuq+dxMEwEavFIHvhmUjlV7t1m9RLC4duNkQhrlaj3EitIr5pChnjEAq7X6qGIgg2Kd5R79mV/7dM5UPUSTjv8/0KYpXnX4JKzSOqWUzP+6tqufA3DipwGmQRHuZ88Z9l80M/8dmtNL9FRftPOapJ/90xiE3lvjUhaDuEfjOVHw7EeQpn/g9abgv+uKIAIqwchfu9ajKh8fTLF09dOuRY4osStp3rc9VE4jn1pEFvVN7MDNR3OOJyFdFqww7XC7hSJgqZ5QDsiCUZEASlNBmMrl1FcukPb0zOJI1ADqBo+psGkwKKL+mEGznXi0LjFmWvN4IcBOlUg9EcQYxjJmnm1vsfEvwghYLtYqPt+zQFIENkENDtizcxSHbVyhRnzYxpOzv5tE1uROjOmFY3eSKF1Y0tswTAOpMzvwS8LgprKeSW534NTcT88barci1vq97u4avtqL/Hld/laGZunmb3VEPuiWLDcCYCm4ji0aG58IYYoIlMip2P+i4ql855wd6RSxfMg8e0mWUnXVM9Bcbp+GuRndBJEAHYhlktdmeCZi0BrOL8tL44CUreciwvAfhSeTZKelWpsvR4KAP7n3UpkJydYxVsd6cbr92Lkk2n3y78YvI8o9I+fu7eCC5S1Z9M1kAA5oSqz4bv8XmDxmYMvVKGRsNZehJERXYq3eeJix1hVncQIY4CIjLTPigrRAIW1nrax+pao5Tl4CMIH/Sz3rYUREXlArOKK8hsQdG+XeDp8IMFT8KQ3lLKGrH99ST8WIeJBQrQZWObAYJ8AGkV69+qH6dHJ39Hlp9QM7lvNBY3I60TJ6Ps+xc4UL0kXbvxIYyJwz8DfsnVM02LA4HUb3CNcSyn3sb2mrGPB1Do2e8ngG9vNsenvk0rfs07Z7QXKEnfnWAsctPjdYv90uC7CrzwyJ13jax3bLNC+A2bZ3wVHL3qtsWGimKSxn4aEw6bBuXR2RSbP0g1fdgmJprCJPYLgxVVT/gA/p9URMhoUmY4+S8ztdlTkpjGRSFaNNEkcl60vfDn60AGl9ni+BILKyZkwBLd8CNi7G00S7OkK3TGezI7ok2MUGyl6PjpJ49H5xPtJHF2tYk08yRhLAhZ8JI7ZvkCakMcwgnLnjrGh28ZZFXNtNIfPDV5sySxHGfFDtzP7fh+aNN5umQe232v0wLJnCqwzOsS3JusN/sFH1SzDedBs5kJTSJYk9kgA+nRheQ955fynxIR4DrDF0MBMGCSqGSIb3DQEJFTEGBAQBAAAAMF0GCSsGAQQBgjcRATFQHk4ATQBpAGMAcgBvAHMAbwBmAHQAIABTAG8AZgB0AHcAYQByAGUAIABLAGUAeQAgAFMAdABvAHIAYQBnAGUAIABQAHIAbwB2AGkAZABlAHIwggN/BgkqhkiG9w0BBwagggNwMIIDbAIBADCCA2UGCSqGSIb3DQEHATAcBgoqhkiG9w0BDAEDMA4ECBkj6Hsy7xK2AgIH0ICCAzi2w/oYEVH+xPe74tRYClp90+R/DP4C+m7ZlUoEUa80ZOkvc8+TL5nnD5CKzCCCQuvGLbZLt9YnVuabQNa4USGgiRAaFbeg/p4rPUjX9auwP2C4hHZxJNCwvTejZ9pCHZBYGMt0jM336OD+Gja3VpD0cDSIDXNI6ieaq4fUfYX7fYR62TVPnT+AtPBGVDS0f86BRRQ2pb8NjpATegUJkjpkGXUy+colFGjm3yx3h2ekXg9oBx7VW9OQg2W9j87d5VbEQqGWmSkgh2GA5cBc9kPdCjSVuOoAw6BjhpgIx+cyEsn7+deHpyPSn/rXsf6saq527vGe4pe7ina+xnp7Y/wttkTNcbRJfHLMcNVOt0Nvp3zTsYz9WTfeWjWuP3t3N0S3vr9f/0PSuhaO7FkLNSLi++qeKvHhSEHilLQexPe9e749S1FApNVuB2FKoL7JhZCEgPIKpILmUGpTKe4SDVt+7FBOGQwKSyqUUU+AIyrtD8djjAmO64IpljIUCiW1oeE0VKFoJx5srEZIAEJGP2LQLJcKgk36EFGQKggzA6l1jmVe+XyKPPbPAI1oBKMhRtJLr8xor9JyuW2CgqUXSAnVKCaVR3L5FndcJyT+ERdbTnXPk5nE9yoqRy+XkuT9KqWkT+iF4rTkcs6oms8VmJBWStT/cUQAXGvalwcKP3muYxYqb44gZFtqJgQpOwWgbF2wsHRxknuU0Gd820aInHhFU4zZxRuERmjRNpYIPrMNUqWOOhP+bnJS0qjZhNVlQ8z6ry1HecT+d7jZujoJBeYm8CxhnE4uu1dBzDgAkbWi0iyQQsycCF36LwO29f67tjv3FeWOL6304gsUcp5uidL8nE7LlOQl5cifoko5QIsqNKZE9nBQ6mGvkOalJn4Yxkv+hCcN/HREGfhwjqVHP/Vfq6wZjNB9st/WMYnDxciPZjTQ+cc6D21NMUX/1uHngcC6P9/phK6mlKJsEF8gQhRfOdcfTdzL/F6Vu/6qvysXVV5QG37mvcR1b+BlpKOY9avrVyPu55x03QNnmapylMY1XuxZY23ymWWMCmAbpxi6hVk4X60O1qKDe/9LVwMy2CTp4+a0nu3DaDA7MB8wBwYFKw4DAhoEFBJYF8a5mk8Ixtv6RcitfrTxvXLyBBTP/+odBIPOlO/8aLnLPRnLUOwSKgICB9A= - -# OpenId Connect Signging Certificate -# Note: This is the Digital Signature Certificate, for secutiry purposes you MUST generate a new certificate. -# See: https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html#registering-a-certificate-recommended-for-production-ready-scenarios -RESGRID__OidcConfig__SigningCert=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 - -# ----------------------------------------------- -# --- Outbound Email Config Section ------------- -# ----------------------------------------------- - -# Email address that emails generated by the system will use (for SMTP and Postmark) -RESGRID__OutboundEmailServerConfig__FromMail=resgrid@yourcompany.local - -# ----------------------------------------------- -# --- SMTP Email Config Section ----------------- -# ----------------------------------------------- - -# Use SSL\TLS to connect to the SMTP server -# true => Use SSL\TLS -# false => Disabled -RESGRID__OutboundEmailServerConfig__EnableSsl=false - -# SMTP Server Hostname -RESGRID__OutboundEmailServerConfig__Host=mailserver - -# SMTP Server Port (Commonly 25 for non-ssl and 587 for ssl) -RESGRID__OutboundEmailServerConfig__Port=25 - -# SMTP Server Login Username -RESGRID__OutboundEmailServerConfig__UserName= - -# SMTP Server Login Password -RESGRID__OutboundEmailServerConfig__Password= - -# ----------------------------------------------- -# --- Postmark Config Section ------------------- -# ----------------------------------------------- - -# API Key from Postmark (https://postmarkapp.com/support/article/1008-what-are-the-account-and-server-api-tokens) -RESGRID__OutboundEmailServerConfig__PostmarkApiKey= - -# ----------------------------------------------- -# --- Service Bus Config Section ---------------- -# ----------------------------------------------- - -# RabbitMQ Hostname -RESGRID__ServiceBusConfig__RabbitHostname=rabbitmq - -# RabbitMQ Hostname2 (for 2nd host in 3 node RabbitMQ cluster) -# Default is blank -RESGRID__ServiceBusConfig__RabbitHostname2= - -# RabbitMQ Hostname3 (for 3nd host in 3 node RabbitMQ cluster) -# Default is blank -RESGRID__ServiceBusConfig__RabbitHostname3= - -# Username for RabbitMQ -# Note: User must be able to create queues and topics for the / virtual host -RESGRID__ServiceBusConfig__RabbitUsername=resgrid - -# Password for RabbitMQ -RESGRID__ServiceBusConfig__RabbbitPassword=Resgrid321! - -# ----------------------------------------------- -# --- Worker Config Section --------------------- -# ----------------------------------------------- - -# Microsoft SQL Server Connection String for Worker Database -RESGRID__WorkerConfig__WorkerDbConnectionString=Data Source=db;Initial Catalog=ResgridWorkers;User Id=sa;Password=Resgrid123!!;MultipleActiveResultSets=True; - -# Payload key for multiple agents -# Note: Change this key if you are running multiple workers -RESGRID__WorkerConfig__PayloadKey=YDm8wWFm4jVZADvDuaM7xmnCsLnydRrwJL3sxNZchL4nU4mkCgUu3fy87yaKBnPSe8g78qvMvTs3ySWutevavt3QbV7M9vdM - -# Have the Worker run the database Upgrade scripts at startup -# Note: When this option is enabled the worker must startup first and perform the upgrade before other services use the db -RESGRID__DODBUPGRADE=true - -# ----------------------------------------------- -# --- Web Config Section ------------------------ -# ----------------------------------------------- - -# Allowed Ingress network for forwarded headers (k8s config) -RESGRID__WebConfig__IngressProxyNetwork=10.42.0.0 - -# CIDR for Ingress network mask -RESGRID__WebConfig__IngressProxyNetworkCidr=16 - -# ----------------------------------------------- -# --- Asp.Net Section --------------------------- -# ----------------------------------------------- - -ASPNETCORE_ENVIRONMENT=Production -ASPNETCORE_URLS=http://0.0.0.0:80 - -# ----------------------------------------------- -# --- Calendar Config Section ------------------- -# ----------------------------------------------- - -# Enable or disable the external iCal feed endpoint (default: true) -RESGRID__CalendarConfig__ICalFeedEnabled=true - -# PRODID value used in generated iCal files (default: -//Resgrid//Calendar//EN) -RESGRID__CalendarConfig__ICalProductId=-//Resgrid//Calendar//EN - -# How long (in minutes) a feed response can be cached by subscribing clients (default: 15) -RESGRID__CalendarConfig__ICalFeedCacheDurationMinutes=15 -``` - -### System Behavior Config - +The containers must also resolve the public hostnames to your reverse proxy (Compose `extra_hosts`, Kubernetes `hostAliases`, or internal DNS). diff --git a/docs/self-hosted/apps.md b/docs/self-hosted/apps.md new file mode 100644 index 00000000..00f65152 --- /dev/null +++ b/docs/self-hosted/apps.md @@ -0,0 +1,67 @@ +--- +sidebar_position: 7 +title: Connecting the mobile apps +--- + +# Connecting the mobile apps to your server + +The Resgrid **Responder** and **Unit** apps from the App Store and Google Play work with self-hosted servers; you don't need special builds. Each user points the app at your server's API hostname once. + +## Before you start + +- The server's **API** and **events** hostnames must resolve on the phone's network (public DNS, or records on the WiFi router for LAN/offline installs) and present a certificate the phone trusts ([Certificates](./certificates)). +- On **Android**, the apps only trust publicly issued certificates. Use Let's Encrypt or your own public certificate if Android phones must use the apps; the local CA works for Android browsers and for iPhones. +- Create the users in the web app first (**Personnel > Add Person**, or invitations if the server sends email). +- For offline deployments, install the apps while the phones still have internet. + +## Set the server URL + +### Resgrid Responder + +1. Open the app. On the login screen tap **Change Server URL**. +2. In **Server**, choose **Custom**. +3. In **Server URL** enter `https://`, for example `https://dispatchapi.example.com`. Only the base URL: no `/api/v4`, no trailing slash. If the server serves HTTPS on a port other than 443 (`RESGRID_HTTPS_PORT` in `.env`, set when 443 was already taken), add the port: `https://dispatchapi.example.com:8443`. + + A private name such as `https://rgapi.rick.internal` works for iPhones, which trust the local CA. It cannot work for the Android apps: `.internal` is reserved for private networks and no public CA will issue a certificate for it, so Android needs a hostname under a domain you own ([RICK: publicly trusted certificate](./rick#publicly-trusted-certificate-android-apps)). +4. Save, then log in with your Resgrid user name and password. + +### Resgrid Unit + +1. On the login screen tap **Server URL**. +2. Choose **Custom** and enter `https://`, with the port if `RESGRID_HTTPS_PORT` is not 443 (`https://dispatchapi.example.com:8443`). The Unit app only accepts `https://` addresses. +3. Save and log in with the unit's or user's credentials. + +You can change it later under **Settings > Server**; changing it signs you out. When the phone has no internet, the server sheet first tries to reach resgrid.com and can take up to 30 seconds before it lets you choose **Custom**. + +The app reads everything else from your server: the events hub address comes from `RESGRID__SystemBehaviorConfig__ResgridEventingBaseUrl`, so that hostname must be reachable from phones too. + +## On a WiFi network without internet + +When the phone joins a network without internet (a [RICK](./rick) kit, a field LAN): + +- **Android** shows *No internet access*: choose **Stay connected** / **Keep WiFi connection**, and turn off mobile data so Android doesn't route the apps over cellular. Turn off **Private DNS** if it is set to a provider. +- **iPhone** asks to allow access to devices on your **Local Network** the first time: allow it (later: **Settings > Privacy & Security > Local Network**). Turn off iCloud Private Relay or VPNs for that WiFi. + +## What to expect + +| Feature | Online self-hosted server | Offline (laptop, RICK) | +|---|---|---| +| Log in, calls, dispatches, statuses, staffing, personnel and unit lists | Yes | Yes | +| Real-time updates while the app is open (events hub) | Yes | Yes | +| Location sharing to the server | Yes | Yes | +| Messages and chat | Yes (chat needs the Chat feature enabled for the department) | Yes, while the app is open | +| Push notifications when the app is closed | Only with a configured Novu instance and the apps' Firebase/APNs credentials (`RESGRID__ChatConfig__Novu*`); not configured by default | No (needs Google/Apple services) | +| Maps in the apps | Mapbox; set `RESGRID__MappingConfig__ResponderAppMapboxKey` / `UnitAppMapBoxKey` | Only areas already cached on the phone | +| Address search | Needs internet geocoding | No | + +Because push notifications aren't available on a typical self-hosted server, ask responders to keep the app open (and the screen on, with **Keep Alive** in the app settings) during an incident so they receive new dispatches immediately. + +## Troubleshooting + +| Symptom | Fix | +|---|---| +| "Network error" at login on Android | The certificate isn't publicly trusted. Use Let's Encrypt or your own public certificate. | +| Login fails on iPhone | Install and enable the CA profile ([Certificates](./certificates#iphone-and-ipad)), check the Local Network permission. | +| Logs in but nothing updates | The events hostname doesn't resolve or isn't trusted from the phone. | +| "Invalid URL" in the Unit app | Use `https://`; the Unit app rejects `http://` servers. | +| Can't resolve the server name | The phone uses another DNS (Private DNS, VPN, cellular). Fix the router records and the phone's DNS settings. | diff --git a/docs/self-hosted/certificates.md b/docs/self-hosted/certificates.md new file mode 100644 index 00000000..1e4d4aad --- /dev/null +++ b/docs/self-hosted/certificates.md @@ -0,0 +1,93 @@ +--- +sidebar_position: 6 +title: Certificates and hostnames +--- + +# Certificates and hostnames + +Resgrid always runs over HTTPS on **three hostnames**: the web app, the API and the events hub (for example `dispatch.example.com`, `dispatchapi.example.com`, `dispatchevents.example.com`). Browsers and the mobile apps connect to all three, so on every device each hostname must: + +1. resolve to your server (DNS or a hosts-file entry), and +2. present a certificate the device trusts. + +If either fails for the API or events hostname, the web app loads but logging in, chat, maps or live updates fail. + +## Choosing a certificate mode + +| Mode (`RESGRID_TLS` in `.env`) | When | What devices need | +|---|---|---| +| An email address → **Let's Encrypt** | Server reachable from the internet on ports 80 and 443, public DNS records | Nothing | +| **Your own certificate** (`/certs/site/fullchain.pem /certs/site/privkey.pem`) | You have a certificate from your organization's CA, a wildcard, or one obtained with a DNS challenge | Whatever already trusts that certificate. Public certificates need nothing. | +| `internal` → **Resgrid local CA** | LAN-only and offline installs (laptop, RICK) | Install the local CA certificate once | + +The installers set this up for you. For your own certificate, copy the full chain and key to `certs/site/fullchain.pem` and `certs/site/privkey.pem` (or pass `RESGRID_TLS_CERT_FILE` / `RESGRID_TLS_KEY_FILE` to `setup.sh`). The certificate must cover all three hostnames. + +If that certificate comes from your organization's own CA rather than a public one, trusting it on client devices is not enough: the containers call the public hostnames themselves (the web app's server-side calls to the API), so they must trust the CA too, or those calls fail. Mount the CA certificate into the containers and add its directory to `SSL_CERT_DIR` — [Trusting a private certificate authority](../reference/docker#trusting-a-private-certificate-authority). Do not turn certificate checks off instead: `RESGRID__ApiConfig__BypassSslChecks=true` makes the web, API and events containers refuse to start in Production. + +:::info Android apps need a public certificate +The Resgrid **Responder** and **Unit** apps on Android only trust certificates from public certificate authorities; they ignore CAs installed by the user. If Android phones must use the apps, use Let's Encrypt or a publicly trusted certificate. Browsers on Android, and iPhones, can use the local CA. The [RICK guide](./rick#publicly-trusted-certificate-android-apps) shows how to get a public certificate for a kit that is never online. +::: + +## The Resgrid local CA + +With `internal`, setup creates a small certificate authority: + +| File | Purpose | +|---|---| +| `certs/ca/resgrid-root-ca.crt` | The CA certificate. Install it on clients. It is public. | +| `certs/private/resgrid-root-ca.key` | The CA private key. Never share it; anyone with it can create certificates your devices trust. | + +Caddy issues and renews the site certificates for the three hostnames from this CA by itself. The Resgrid containers trust the CA too (`SSL_CERT_DIR` in `.env`), so the web app's server-side calls to the API work without turning certificate checks off. The CA certificate is valid for 10 years. + +## Installing the CA certificate + +Copy `resgrid-root-ca.crt` to the device (USB stick, email, file share, `scp`), then: + +### Windows + +1. Double-click `resgrid-root-ca.crt` and choose **Install Certificate**. +2. Choose **Local Machine** (or **Current User** if you are not an administrator) and **Next**. +3. Choose **Place all certificates in the following store**, **Browse**, **Trusted Root Certification Authorities**, **OK**, **Next**, **Finish**. +4. Restart the browser. Chrome and Edge use the Windows store. In Firefox open `about:config` and set `security.enterprise_roots.enabled` to `true`. + +The [laptop installer](./laptop) does this for your account automatically. + +### macOS + +1. Double-click `resgrid-root-ca.crt`; Keychain Access opens and adds it to the **login** keychain. +2. Double-click **Resgrid Local CA** in the list, expand **Trust**, and set **When using this certificate** to **Always Trust**. Close the window and enter your password. + +### iPhone and iPad + +1. AirDrop or email the file to the device and open it; iOS says **Profile Downloaded**. +2. **Settings > General > VPN & Device Management**, select the **Resgrid Local CA** profile, **Install**. +3. **Settings > General > About > Certificate Trust Settings**, turn on **Resgrid Local CA**. + +### Android + +**Settings > Security > More security settings > Encryption & credentials > Install a certificate > CA certificate**, then pick the file (menu names vary by manufacturer). This makes Chrome trust the server; the Resgrid apps still need a public certificate (see above). + +### Linux + +```bash +sudo cp resgrid-root-ca.crt /usr/local/share/ca-certificates/ +sudo update-ca-certificates +``` + +Firefox and Chrome on Linux keep their own store: import the file under **Settings > Privacy & Security > Certificates > View Certificates > Authorities > Import** (Firefox) or **Settings > Privacy and security > Security > Manage certificates** (Chrome). + +## Hostnames without DNS + +Without DNS records, add one line per device to its hosts file: `C:\Windows\System32\drivers\etc\hosts` on Windows (edit Notepad as Administrator), `/etc/hosts` on macOS and Linux. + +```text +192.168.1.50 rg.mylocal rgapi.mylocal rgevents.mylocal +``` + +Phones can't edit a hosts file; create the records on your router or DNS server instead. Most routers call this *DNS rewrites*, *custom hosts* or *static DNS*. + +Hostname tips: + +- Names under `.localhost` (`rg.localhost`) always point at the computer itself in Chrome, Edge and Firefox; the [laptop install](./laptop) uses them so no hosts-file edit is needed. +- Avoid `.local`: iPhones and Macs resolve it only with multicast DNS and ignore router records for it. `.internal` is reserved for private networks and works everywhere. +- Every device's clock must be right: certificates are rejected when a clock is far off. diff --git a/docs/self-hosted/features-and-upgrades.md b/docs/self-hosted/features-and-upgrades.md new file mode 100644 index 00000000..b73a0708 --- /dev/null +++ b/docs/self-hosted/features-and-upgrades.md @@ -0,0 +1,175 @@ +--- +sidebar_position: 8 +title: Optional features and upgrades +--- + +# Optional features and upgrades + +Every [resgrid-setup](https://github.com/Resgrid/resgrid-setup) deployment (single server, Windows laptop, Kubernetes and the Incident Command Kit) uses the same two mechanisms after the first install: + +- an **optional features** menu that switches Resgrid modules such as Records, Unified search and Chat on or off for the whole server, together with any settings or extra services they need; +- **re-running setup** as the way to upgrade and repair an install. It is safe to run any time and never deletes data. + +## Optional features + +Resgrid ships several modules behind feature flags that are off on a new server. Setup asks about each one at the end of the first install, and you can change them later: + +| Deployment | Command | +|---|---| +| Single server (`master`) | `./setup.sh features` | +| Windows laptop (`laptop`) | `.\resgrid.ps1 features` | +| Kubernetes (`multi`) | `./setup.sh features` | +| Incident Command Kit (`rick`) | `./rick.sh features` (on rick1) | + +Each feature is one question, even when it needs many flags: + +```text +==> Optional features + Resgrid ships some modules switched off. Answer y to turn a feature on for + every department on this server, n to turn it off; Enter takes the answer + in brackets. Run this again any time with: ./setup.sh features + + [1/10] Records (RMS) (now: off) + Incident and field records (replaces Logs), records in the Responder, Unit and Dispatch apps, + prevention (occupancy, inspections, hydrants, permits, community risk reduction), + investigations, quality review and records analytics. + After switching it on, a department administrator activates Records in the department settings. + Turn on Records (RMS)? (y/n) [n]: y + + [2/10] Unified search (now: off) + Search across calls, personnel, units, contacts and records, plus the command palette. + Uses Resgrid's built-in search index, stored in the RustFS object store. + Turn on Unified search? (y/n) [y]: y +... + Not available on self-hosted servers (Resgrid subscription add-ons): Enhanced AI, Workforce and + Business Operations, Incident Back Office, Maintenance and work orders (Readiness Pro), Advanced + Data Protection, Resgrid-operated services (managed migration, Stripe Connect payments) +==> Records (RMS): on +==> Unified search: on +``` + +The default answer is the current state, or "y" when a feature you just turned on suggests it (Records suggests Unified search; Department import suggests virus scanning). + +### The features + +| Feature | What setup switches and configures | +|---|---| +| **Records (RMS)** | All 13 `Records.*` flags: incident and field records, records in the Responder, Unit and Dispatch apps, prevention (occupancy, inspections, hydrants, permits, community risk reduction), investigations, quality review, analytics. A department administrator then activates Records in the department settings. | +| **Unified search** | `Search.Unified`, and `RESGRID__SearchConfig__*` so the search index is kept in the bundled RustFS object store (bucket `resgrid-search`). The worker builds each department's index within a few minutes. | +| **Chat** | `Chat.System`: department channels, direct messages and incident chat in the web and mobile apps. | +| **Run cards** | Run cards, station-based dispatching, closest-unit response, move-up recommendations and conditional run cards. | +| **Checklists** | Unit, station and inventory checklists. | +| **Deployments** | Out-of-area deployments: rosters, daily time reports, expenses, attachments. | +| **Setup wizard and admin guidance** | Guided department setup, the setup report and administrator recommendations. | +| **Department import and migration** | Imports of personnel, units, stations, contacts, inventory, history and records from spreadsheets and other CAD/RMS systems. | +| **Attachment virus scanning** | Starts a ClamAV service (about 1.5 GB of RAM) and turns on `RESGRID__AttachmentScanningConfig__Enabled`. ClamAV brings its signatures with it, so scanning also works offline. | +| **Text-message commands (Twilio)** | Routes inbound SMS through the Resgrid chatbot; setup asks for the Twilio account SID, auth token and number. Needs internet access and a public API address. | + +Resgrid's paid add-ons (Enhanced AI, Workforce and Business Operations, Incident Back Office, Readiness Pro work orders, Advanced Data Protection) depend on Resgrid's hosted billing service. Setup lists them but doesn't offer them on self-hosted servers. + +A feature is switched for **every department on the server** (the `featureflags` table of the `resgrid` database). Setup clears Resgrid's flag cache in Redis, so the change shows on the next page load; new menu entries such as **Records** and **Chat** appear in the web app, and the search box in the header searches everything: + +![Unified search results and the Records menu after turning the features on](/img/self-hosted/features-search.png) + +### Without questions + +Give the features that should be on; every other feature is switched off: + +```bash +RESGRID_FEATURES=records,search,chat ./setup.sh features # or: all, none +``` + +On the laptop: `.\setup.ps1 -Features records,search,chat`. The same variable works on the first install (`RESGRID_FEATURES=records,search ./setup.sh`), which is handy for automated or repeated installs. `--all` (`-All` on the laptop) also offers individual flags that no feature in the catalog covers, for testing new Core features. + +### What setup remembers + +Setup keeps two entries in `.env` (`deploy/generated/resgrid.env` on Kubernetes): + +- `RESGRID_FEATURES`: the features you turned on; +- `RESGRID_FEATURES_OFFERED`: the features it has asked you about. + +When setup runs again (an upgrade, see below) it uses them to: + +- ask **only about features that are new** in that version. With nothing new it asks one question, "Change the optional features?", which defaults to no; +- **switch your features fully back on** when their flags are off or only partly on. That happens after restoring an older database, or when a new Resgrid version adds a flag to a feature you use (new flags start off). + +## Upgrade and repair + +Running setup again is the upgrade. Back up first, then: + +| Deployment | Upgrade | +|---|---| +| Single server | `cd ~/resgrid && ./setup.sh upgrade`, or the install one-liner `curl -fsSL https://raw.githubusercontent.com/Resgrid/resgrid-setup/master/setup.sh \| bash` | +| Windows laptop | `.\resgrid.ps1 upgrade` | +| Kubernetes | `./setup.sh upgrade` | +| Incident Command Kit | `./rick.sh upgrade` on rick1 (with internet) | + +`upgrade` gets the newest setup files (`git pull`, or the archive download without git; a newer setup script takes over from the old one) and then runs setup again. Without internet, `RESGRID_NO_PULL=1 ./setup.sh` (`.\setup.ps1 -NoPull`) repairs the configuration and restarts with the images already present. + +On an existing install setup **never replaces a value you set, never rotates a secret your data depends on and never deletes data**. It: + +1. backs up the settings file (`.env.backup-`); +2. adds the settings introduced since you installed. New secrets get random values; everything else gets the template default, with the example node addresses replaced by yours on the kit; +3. fills in required values that are empty and fixes values known to break (old container addresses, `BypassSslChecks=true`, a placeholder events secret); +4. recreates generated files that are missing: local certificate authority, OpenID Connect certificates, the kit's DNS file, the Kubernetes ingress certificate; +5. keeps the data of older versions (next section); +6. pulls the new images, starts new services, removes retired ones and restarts. The worker migrates the database schema; +7. asks about new optional features and re-applies yours. + +After pulling a new version, always run setup rather than only `docker compose up -d`, so these steps happen. Rolling back after a database migration means restoring the backup that matches the older images. + +Each deployment also repairs what is specific to it: + +- **Kubernetes**: the bucket job runs again on every apply, so new storage buckets are created. StatefulSet pods that never became ready under an older definition are restarted; Kubernetes doesn't replace those on its own. The optional services listed in `RESGRID_OPTIONAL` are applied, and the ones setup applied before but that are no longer listed are removed. +- **Incident Command Kit**: `rollout` copies the configuration to every node over SSH, starts the nodes in order (data, infra, app, edge) and waits for the database before asking about features. `sync` never touches a node's `docker-data/`, `backups/` or `bundle/` folders. + +### Upgrading an install created before October 2026 + +Older versions of resgrid-setup tracked `.env` in git, used `NGINX_*` setting names and fixed container IP addresses, and stored PostgreSQL data in other places. Upgrade them the same way; setup recognizes the old layout: + +- the tracked `.env` is set aside during `git pull` and put back; +- `NGINX_RESGRID_WEB_URL` and the other `NGINX_*` settings become `RESGRID_WEB_HOST`, `RESGRID_API_HOST`, `RESGRID_EVENTS_HOST` and `RESGRID_TLS`, and container IP addresses become service names; +- on the Incident Command Kit, the three-node settings (web, infra, database) are mapped onto the four roles: the old web node runs edge and app, so every address stays valid; +- your data is copied into the new PostgreSQL 18 database, **once and only while the new database is empty**, so nothing is overwritten: + - single server: the `docker-data/sql` folder of installs up to April 2025 (PostgreSQL 17). The copy uses that version's image, so it needs internet access; + - a database container of the old stack is stopped and its data directory saved first: to `backups/legacy-pgdata-*.tar` on a server, or as the image `resgrid-legacy-db:` on the laptop; + - Incident Command Kit: the database container of the three-node kit, on the data node; +- the original data isn't changed, and a dump is kept in `backups/legacy-db-*.sql.gz`. Set `RESGRID_MIGRATE_LEGACY_DB=0` to skip the copy; +- old containers under a different project name are stopped, not removed, so their ports are free. Remove them once the new version works. + +## Maintaining the feature list + +This section is for people who maintain resgrid-setup or a fork of it. The questions come from one file, [`features/catalog.ini`](https://github.com/Resgrid/resgrid-setup/blob/master/features/catalog.ini), shared by all four branches. `lib/features.sh` (and `scripts/features.ps1` on the laptop) read it, so adding, regrouping or retiring a feature is an edit to the catalog only: + +```ini +[records] +title=Records (RMS) +about=Incident and field records (replaces Logs), records in the Responder, Unit and Dispatch apps, +about=prevention (occupancy, inspections, hydrants, permits, community risk reduction), +about=investigations, quality review and records analytics. +category=Records +recommends=search + +[scanning] +title=Attachment virus scanning (ClamAV) +about=Scans uploaded files with ClamAV, in an extra container that needs about 1.5 GB of RAM. +profile=clamav +setting=RESGRID__AttachmentScanningConfig__Enabled=true +off=RESGRID__AttachmentScanningConfig__Enabled=false +``` + +| Key | Meaning | +|---|---| +| `title=`, `about=` | The question and its explanation (`about=` may repeat, one line each) | +| `flags=` | Flag keys, separated by spaces | +| `category=`, `exclude=` | Every flag in a category, including flags Core adds later, except the keys listed | +| `requires=` | Sections switched on together with this one | +| `recommends=` | Sections offered with "y" as the default once this one is on | +| `setting=KEY=value` | Written to the settings file when the feature is turned on; `{OTHER_KEY}` copies another setting's value | +| `off=KEY=value` | Written when the feature is turned off | +| `ask=KEY\|question\|default` | A value setup asks for when the feature is turned on | +| `profile=` | Optional service that runs while the feature is on: a Compose profile, or a file in `deploy/optional/` on Kubernetes | +| `online=yes` | Shown as needing internet access | +| `available=no` | A hosted-only add-on: listed, never switched | + +Setup reads the flags from the database, so the catalog keeps working when Core changes. Flags that no longer exist are skipped, and a section with no flags in the database isn't shown. When Core retires a feature for good, delete its section, and setup stops asking about it. `./setup.sh features --all` lists flags that no section covers, which shows what a new Core version added. diff --git a/docs/self-hosted/installation.md b/docs/self-hosted/installation.md index 6fd58937..f4eeb6a2 100644 --- a/docs/self-hosted/installation.md +++ b/docs/self-hosted/installation.md @@ -1,153 +1,299 @@ --- sidebar_position: 2 +title: Installation (single server) --- # Self-hosted installation -Resgrid Core has four maintained deployment layouts in the [resgrid-setup repository](https://github.com/Resgrid/resgrid-setup). Choose the layout that matches the environment; the branches are intentionally different and are not interchangeable. +Resgrid Core is open source and can run on your own hardware. The [resgrid-setup repository](https://github.com/Resgrid/resgrid-setup) has four ready-made, tested deployments; pick the one that matches where Resgrid will run: -| Branch | Use case | Orchestrator | Hosts | -|---|---|---|---| -| `master` | General self-hosted evaluation or small single-server install | Docker Compose | One 64-bit Linux server (Windows/macOS through WSL2 is also supported) | -| `laptop` | Local Windows development, training, or single-computer field use | Docker Desktop Compose | One Windows 10/11 computer | -| `rick` | Portable Resgrid Incident Command Kit | Compose split into database, infrastructure, and application roles | Three 64-bit Raspberry Pi-class nodes; current app images use AMD64 emulation on the web Pi | -| `multi` | Kubernetes or k3s | Kustomize (`kubectl apply -k`) | A cluster with persistent storage, ingress, and at least one AMD64 worker | +| Deployment | Branch | Use it for | Runs on | Network | +|---|---|---|---|---| +| **Single server** (this page) | `master` | A department or agency server | One 64-bit Linux server with Docker | LAN or internet | +| [Windows laptop](./laptop) | `laptop` | One person on one computer: a command post laptop, a disconnected incident, training | Windows 10/11 + Docker Desktop | Fully offline after setup; local browser only | +| [Kubernetes / k3s](./multi) | `multi` | Production across several servers, scaling, maintenance without downtime | A Kubernetes or k3s cluster | LAN or internet | +| [Incident Command Kit (RICK)](./rick) | `rick` | A portable, battery-powered kit for a base camp or command vehicle | 4 × Raspberry Pi 5 + WiFi hotspot router | Fully offline; responders' phones join the hotspot | -See [Windows laptop](./laptop), [RICK](./rick), or [Kubernetes/k3s](./multi) for those specialized layouts. The rest of this page covers `master`. +All four run the same Resgrid services and use the same configuration settings; they differ in how the services are spread out and reached. Whichever you choose, also read [Certificates](./certificates) and [Connecting the mobile apps](./apps). :::danger Warranty and operational responsibility -The self-hosted version is provided without warranty or a guarantee of suitability. The supplied manifests are a starting point, not a substitute for capacity planning, security review, monitoring, tested backups, or a disaster-recovery plan. Do not use template credentials in a real deployment. +The self-hosted version is provided without warranty or a guarantee of suitability. The supplied deployments are a starting point, not a substitute for capacity planning, security review, monitoring, tested backups and a recovery plan. Never run a real system on the template credentials. ::: -## What the single-server stack runs +## What runs -The current stack includes the web application, API, events hub, background worker and database migrations, text-to-speech service, MCP server, PostgreSQL, Redis, RabbitMQ, RustFS object storage, and Caddy ingress. Hardware GPS tracking and inbound-email dispatch are optional Compose profiles. +Single-server stack: Caddy routes the three Resgrid hostnames to the web app, API and events hub, which use PostgreSQL, Redis, RabbitMQ and RustFS -MongoDB and Microsoft SQL Server are no longer part of the standard stack. The main, OIDC, worker, and document databases all use PostgreSQL. +| Service | Purpose | Port on the server | +|---|---|---| +| `caddy` | HTTPS for the three Resgrid hostnames, with automatic certificates | 80, 443 | +| `web` | The Resgrid web application | 5151 | +| `api` | REST API used by the web app and the mobile apps; also the login (OpenID Connect) server | 5152 | +| `events` | Real-time updates (SignalR) | 5153 | +| `worker` | Background jobs, notifications and the **database schema migrations** | — | +| `tts` | Text-to-speech for dispatch audio | 5154 | +| `mcp` | Model Context Protocol server for AI assistants | 5155 (published on every interface; keep it off untrusted networks with a firewall) | +| `db` | PostgreSQL 18: `resgrid`, `resgridoidc`, `resgridworkers`, `resgriddoc` | 127.0.0.1:5432 | +| `redis` | Cache, real-time backplane, web session keys | — | +| `rabbitmq` | Message bus | 127.0.0.1:5160 (management UI) | +| `rustfs` | S3-compatible object storage (TTS audio, imports) | — | + +Optional: offline map tiles (`maps` profile), hardware GPS trackers (`tracking`), inbound email dispatch (`relay`). MongoDB and Microsoft SQL Server are no longer used; every database is PostgreSQL. + +`db` and `rabbitmq` are bound to `127.0.0.1` by Compose. The others publish their port on every interface of the server, so on an internet-facing host let only 80 and 443 in from the internet (plus 25 if you run the relay), and keep the rest for your LAN or VPN: 5155 for MCP, and 5004, 5023 and 5027 for GPS trackers when those optional services are on. ## Requirements -- A 64-bit Linux server. Ubuntu Server 24.04 LTS is a practical baseline. -- Docker Engine and Docker Compose v2. -- OpenSSL, Git or curl, and outbound registry access during installation. -- At least 4 CPU cores, 16 GB RAM, and 40 GB storage for evaluation. Size production systems from measured workload and retention needs. -- Three hostnames that resolve to the server: web, API, and events. -- TCP 80/443 to the server or an upstream load balancer/reverse proxy. -- An SMTP or Postmark account if the installation must send email. +- A 64-bit Linux server. Ubuntu Server 24.04 LTS or Debian 12/13 is a good baseline. +- [Docker Engine](https://docs.docker.com/engine/install/) with the Compose v2 plugin, plus `openssl` and `git` (or `curl`). +- At least 4 CPU cores, 8 GB RAM (16 GB recommended) and 40 GB of disk. The stack idles at about 1 GB of RAM; size production systems from measured load and retention. +- Three hostnames that resolve to the server: **web**, **API** and **events**, for example `dispatch.example.com`, `dispatchapi.example.com`, `dispatchevents.example.com`. +- TCP 80 and 443 to the server. +- Internet access during installation (about 6 GB of images). +- An SMTP server or Postmark account if Resgrid must send email. + +The infrastructure images are Docker Hardened Images from `dhi.io`. If a pull reports an authorization error run `docker login dhi.io`; if Docker Hub reports `429 Too Many Requests`, run `docker login` (anonymous pulls are rate limited). + +## Step 1: Prepare the server -The stack uses Docker Hardened Images from `dhi.io`. If a pull reports an authorization error, authenticate with a Docker account entitled to those images before continuing: +Install Docker and check it works: ```bash -docker login dhi.io +curl -fsSL https://get.docker.com | sudo sh +sudo usermod -aG docker $USER # log out and back in +docker info +docker compose version +openssl version ``` -## Automated install +Create the DNS records for your three hostnames (A records to the server's address). For a LAN-only server without DNS you can use the defaults `rg.mylocal`, `rgapi.mylocal`, `rgevents.mylocal` and add them to each client's hosts file later. -With Docker running: +## Step 2: Run the installer ```bash curl -fsSL https://raw.githubusercontent.com/Resgrid/resgrid-setup/master/setup.sh | bash ``` -The installer downloads the `master` branch, asks for the three hostnames and TLS mode, creates unique infrastructure/application secrets, generates new OpenIddict signing and encryption certificates, writes `.env`, and starts the stack. +The installer downloads the stack to `~/resgrid` and asks four questions: + +```text +==> Configuration (press Enter to accept the defaults). +Web app hostname [rg.mylocal]: dispatch.example.com +API hostname [rgapi.mylocal]: dispatchapi.example.com +Events hostname [rgevents.mylocal]: dispatchevents.example.com +Let's Encrypt email ('internal' = local CA, for LAN/offline) [internal]: admin@example.com +``` -For a LAN-only evaluation, accept the defaults (`rg.mylocal`, `rgapi.mylocal`, `rgevents.mylocal`, and `internal`). Add all three names to every client's hosts file, pointing at the server address: +- Give an **email address** when the server is reachable from the internet with public DNS: Caddy gets and renews Let's Encrypt certificates for all three hostnames. +- Answer **internal** for LAN-only or offline servers: Resgrid's own certificate authority issues the certificates, and you install its certificate on each client once ([Certificates](./certificates)). + +It then generates every password, passphrase and encryption key into `.env`, creates the OpenID Connect certificates and the local certificate authority (`certs/`), pulls the images, starts the stack and waits until Resgrid answers: + +```text +==> Generating passwords, keys and certificates... +==> Creating the Resgrid local certificate authority (10 year validity)... +==> Configuration written to /home/admin/resgrid/.env (it holds all your secrets: back it up and keep it private). +==> Pulling container images (the first run downloads several GB)... +==> Starting Resgrid... +==> Waiting for Resgrid to answer (the first start creates the database, this can take 5-10 minutes)... +==> Resgrid is up. +==> Waiting for the database migration to finish... +``` + +Last, it walks you through the **optional features**: modules such as Records, Unified search and Chat that ship switched off. One question per feature turns on every flag, setting and extra service it needs ([Optional features and upgrades](./features-and-upgrades)): ```text -192.168.1.50 rg.mylocal rgapi.mylocal rgevents.mylocal +==> Optional features + Resgrid ships some modules switched off. Answer y to turn a feature on for + every department on this server, n to turn it off; Enter takes the answer + in brackets. Run this again any time with: ./setup.sh features + + [1/10] Records (RMS) (now: off) + Incident and field records (replaces Logs), records in the Responder, Unit and Dispatch apps, + prevention (occupancy, inspections, hydrants, permits, community risk reduction), + investigations, quality review and records analytics. + After switching it on, a department administrator activates Records in the department settings. + Turn on Records (RMS)? (y/n) [n]: y +... +==> Records (RMS): on ``` -For an internet-reachable install, use real DNS records and provide an email address so Caddy can request certificates. Ports 80 and 443 must reach Caddy. +```text + Open: https://dispatch.example.com + then choose "Sign Up" to create the first account and department. +``` -Non-interactive example: +Unattended installs can answer every question with a variable: ```bash -RESGRID_WEB_URL=dispatch.example.com \ -RESGRID_API_URL=dispatchapi.example.com \ -RESGRID_EVENTS_URL=dispatchevents.example.com \ -RESGRID_LETSENCRYPT_EMAIL=admin@example.com \ -bash setup.sh +curl -fsSL https://raw.githubusercontent.com/Resgrid/resgrid-setup/master/setup.sh \ + | RESGRID_WEB_HOST=dispatch.example.com \ + RESGRID_API_HOST=dispatchapi.example.com \ + RESGRID_EVENTS_HOST=dispatchevents.example.com \ + RESGRID_TLS=admin@example.com \ + RESGRID_FEATURES=records,search \ + bash ``` -Set `RESGRID_NO_START=1` to generate configuration without pulling or starting containers. +The install one-liner pipes the script straight into Bash and leaves no `setup.sh` behind, so use that same form for the first unattended install. The variables go on the **`bash`** side of the pipe: assignments apply only to the command they precede, so putting them in front of `curl` sets them for `curl` and the installer never sees them. Once the stack is in `~/resgrid`, later runs use `cd ~/resgrid && RESGRID_FEATURES=... bash setup.sh`. + +Other variables: `RESGRID_INSTALL_DIR`, `RESGRID_HTTP_PORT` / `RESGRID_HTTPS_PORT` (if 80/443 are taken; Let's Encrypt then can't be used), `RESGRID_TLS_CERT_FILE` + `RESGRID_TLS_KEY_FILE` (your own certificate), `RESGRID_FEATURES` (optional features, comma separated, or `all` / `none`), `RESGRID_NONINTERACTIVE=1` (never ask), `RESGRID_NO_START=1` (configure only). + +:::caution +`.env` holds every secret, including the encryption key for stored data and the token signing certificates. Back it up somewhere safe and private. Losing it makes encrypted data and existing logins unrecoverable. +::: + +## Step 3: Create your department + +1. Open `https://`. The login page has a **Create an account** button. + + ![Resgrid login page with the Create an account button](/img/self-hosted/login.png) + +2. Fill in your name, email, the department name and type, a user name and a password, then choose **Register**. + + ![Sign up form filled in for the first department](/img/self-hosted/sign-up.png) + +3. You are signed in to your new department as its administrator. + + ![Resgrid dashboard after signing up](/img/self-hosted/dashboard.png) + +There is no default administrator account; Sign Up creates the department and its first administrator. Self-hosted sign-up is enabled by `RESGRID__SystemBehaviorConfig__AllowSignupWithoutBilling=true` in `.env`. Once your department exists, set it to `false` and run `docker compose up -d` in `~/resgrid`, so nobody else can create departments on your server. Add people with **Personnel > Add Person** or invitations. + +Continue with the [department setup guide](../configuration/setup): stations, units, roles, statuses, and then [connect the mobile apps](./apps). -## First-start verification +## Step 4: Check that everything works -The worker creates and migrates four databases. First start may take several minutes: +Create a test call: **Calls > New Call**, enter a name, nature and location, then **Create Call**. It appears under **Active Calls** and on the map, and every open browser and app updates in real time. + +![The Calls page showing a newly created call](/img/self-hosted/calls.png) + +From the server: ```bash cd ~/resgrid -docker compose ps -docker compose logs -f worker +docker compose ps # every service "running"; db, redis, rabbitmq "healthy" +docker compose logs -f worker # migrations and background jobs +curl -k https:///health ``` -For an internal certificate, open and accept the certificate for the web, API, and events hostnames. The web client cannot log in if the API/events certificates are still rejected by the browser. +## Configuration -When migrations settle, open the web hostname and use **Sign Up** to create the first account and department. Current releases do not use a shared default administrator login. +Everything is in `~/resgrid/.env`. Resgrid settings use the pattern `RESGRID__{ConfigClass}__{FieldName}` and map to the static classes in [Core/Resgrid.Config](https://github.com/Resgrid/Core/tree/master/Core/Resgrid.Config); see the [Docker reference](../reference/docker). After editing, run `docker compose up -d` and Compose recreates the containers whose settings changed. -Useful endpoints with the template ports: +The settings you are most likely to change: -| Purpose | Endpoint | +| Setting | Purpose | |---|---| -| Web through Caddy | `https://rg.mylocal` | -| API health | `https://rgapi.mylocal/api/health/getcurrent` | -| Web direct diagnostic port | `http://SERVER:5151` | -| API direct diagnostic port | `http://SERVER:5152` | -| Events direct diagnostic port | `http://SERVER:5153` | -| RabbitMQ management | `http://SERVER:5160` | - -MCP's HTTP transport is unauthenticated and published on port 5155 for LAN use. Do not expose it directly to the internet. +| `RESGRID__OutboundEmailServerConfig__Host`, `Port`, `UserName`, `Password`, `EnableSsl`, `FromMail` | SMTP for invitations, password resets and notifications (no mail server is included) | +| `RESGRID__MappingConfig__LeafletTileUrl` | Map tiles for the web map. The OpenStreetMap default is for testing only; use a tile provider or offline maps | +| `RESGRID__MappingConfig__GoogleMapsApiKey`, `What3WordsApiKey` | Optional geocoding | +| `RESGRID__MappingConfig__ResponderAppMapboxKey`, `UnitAppMapBoxKey` | Map tiles in the mobile apps (Mapbox) | +| `RESGRID__WebConfig__RecaptchaPublicKey`, `RecaptchaPrivateKey` | reCAPTCHA on Sign Up and Forgot Password for internet-facing servers | +| `RESGRID__SystemBehaviorConfig__AllowSignupWithoutBilling` | `false` after your department exists | ## Optional services -After configuring `RESGRID__UnitTrackingConfig__*` in `.env`: +**Offline maps** (`maps` profile): import an OpenStreetMap extract for your area once, while online, and the web map no longer needs a tile provider: ```bash -docker compose --profile tracking up -d +cd ~/resgrid +./maps/import.sh https://download.geofabrik.de/north-america/us/nevada-latest.osm.pbf ``` -The profile opens the Queclink, GT06, and Teltonika TCP/UDP ports. Restrict them to expected tracker source networks. +Pick the smallest [Geofabrik extract](https://download.geofabrik.de) that covers your area (a US state takes 10–60 minutes). The import adds `maps` to `COMPOSE_PROFILES` in `.env`, so the tile server starts with the stack from then on. + +Optional services run while their [Compose profile](https://docs.docker.com/compose/how-tos/profiles/) is listed in `COMPOSE_PROFILES` in `.env` (comma separated); `docker compose up -d` reads that setting itself. + +**Virus scanning** (`clamav` profile): turned on with the *Attachment virus scanning* [optional feature](./features-and-upgrades). + +**GPS trackers** (`tracking` profile): set `RESGRID__UnitTrackingConfig__Enabled`, `NativeGatewayEnabled` and at least one of `EnableQueclink`, `EnableGt06`, `EnableTeltonika` to `true`, add `tracking` to `COMPOSE_PROFILES`, then `docker compose up -d`. Trackers connect to TCP/UDP 5004 (Queclink), 5023 (GT06) and 5027 (Teltonika). -After configuring the relay's department and mail-domain settings: +**Inbound email dispatch** (`relay` profile): set `RESGRID__RELAY__Resgrid__DepartmentId` and the dispatch domains (`RESGRID__RELAY__Smtp__DepartmentAddressDomains__0`, and `GroupAddressDomains__0` for group dispatch), point the domains' MX records at the server, add `relay` to `COMPOSE_PROFILES`, then `docker compose up -d`. The relay turns mail addressed to `{dispatchcode}@` into a Resgrid call; it is not an open mail relay, but it accepts any sender and offers no TLS or SMTP authentication, so the recipient-domain list is its only application-level gate. Restrict port 25 to the senders you expect with a firewall, or put a filtering MTA that terminates TLS in front of it, before exposing it to the internet. + +**MCP** for AI assistants listens on port 5155. Users log in inside the protocol, but the HTTP endpoint itself is unauthenticated, and Compose publishes 5155 on every interface of the server. Block the port from untrusted networks with a host firewall, or reach it over a VPN only. + +## Operations ```bash -docker compose --profile relay up -d +cd ~/resgrid +docker compose ps # status +docker compose logs -f api # logs of one service +docker compose restart web # restart one service +docker compose down # stop everything; data stays in Docker volumes +docker compose up -d # start ``` -The relay opens SMTP port 25 and has no SMTP authentication or TLS. Put a filtering MTA or firewall in front of it. +### Optional features -## Operations +```bash +./setup.sh features +``` + +turns optional modules such as Records, Unified search, Chat, Run cards and virus scanning on or off for every department on the server. See [Optional features and upgrades](./features-and-upgrades). + +### Upgrades ```bash -docker compose ps -docker compose logs -f SERVICE -docker compose down -docker compose pull -docker compose up -d +cd ~/resgrid +./setup.sh upgrade ``` -An upgrade is `pull` followed by `up -d`; do not delete every Docker image on the host. The worker applies database migrations during startup. +or the install one-liner again. Re-running setup is the upgrade and is always safe: it gets the newest setup files, backs up `.env`, adds the settings introduced since you installed (new secrets get random values) and repairs missing or broken ones, pulls the new images, starts new services and restarts; the worker migrates the database. It never replaces a value you set or deletes data, and asks only about optional features that are new. Details: [Upgrade and repair](./features-and-upgrades#upgrade-and-repair). Back up first: rolling back after a migration needs the matching database backup. -## Backup and rollback +### Backups -Back up `.env` and keep it encrypted. It contains keys required to decrypt existing data/tokens and to validate OIDC credentials. Take a consistent PostgreSQL dump rather than copying the live database directory: +Back up the database, `.env`, `certs/` and the RustFS object storage: ```bash -docker compose exec -T db pg_dumpall -U resgrid > "resgrid-$(date +%F).sql" +mkdir -p backups +day=$(date +%F) +( + set -e + docker compose exec -T db sh -c 'pg_dumpall -U "$POSTGRES_USER" --clean --if-exists' > backups/resgrid-$day.sql + gzip -f backups/resgrid-$day.sql + cp .env backups/env-$day + tar -czf backups/certs-$day.tar.gz certs + docker compose stop rustfs + docker compose cp rustfs:/data backups/rustfs-$day +) +rc=$? +docker compose start rustfs +[ "$rc" -eq 0 ] || echo "BACKUP INCOMPLETE: see the output above; do not rely on $day" ``` -Also back up RustFS and Caddy data according to your storage and certificate requirements. +The subshell and `set -e` stop at the first failure instead of carrying on to a command that succeeds and leaves a partial backup looking usable; `docker compose start rustfs` runs outside it so object storage comes back even when the copy fails. Compressing only after `pg_dumpall` succeeds matters for the same reason: `pg_dumpall | gzip` returns `gzip`'s status, so a failed dump otherwise leaves a `.sql.gz` that looks valid and is empty or truncated. Before you rely on a backup, check that all four artifacts for that date exist and are a plausible size. -Before upgrading, retain the previous image digests and a pre-upgrade database dump. Rolling application containers back does not reverse schema migrations. If an older release is incompatible with the migrated schema, restore the database backup and matching `.env` before starting the older images. +RustFS holds the TTS audio and import files that database rows point at, so skipping it leaves records referencing objects that no longer exist. The Unified search index is not in the list: the worker rebuilds it. -## Configuration reference +Restore onto an install with the same `.env` and `certs/`: -Container settings use double-underscore environment names such as `RESGRID__DataConfig__DatabaseType`. See [Docker configuration](../reference/docker) for the complete reference. Common pitfalls: +```bash +day=2026-10-27 # the date of the backup you are restoring +docker compose stop web api events worker tts mcp +gunzip -c backups/resgrid-$day.sql.gz | docker compose exec -T db sh -c 'psql -q -U "$POSTGRES_USER" -d postgres' +docker compose stop rustfs +docker compose cp backups/rustfs-$day/. rustfs:/data +docker compose up -d +``` + +Messages that a role or database "already exists" during a restore are expected. Restore the database and the RustFS data from the same day: a database restored on its own points at objects the matching RustFS backup holds. + +### Upgrading an install created before October 2026 -- Enum values are numeric. -- `CoreConnectionString` is required and normally matches `ConnectionString`. -- `DocDatabaseType=1` selects PostgreSQL. -- `DODBUPGRADE=true` belongs on the worker. -- Some historical field names intentionally contain spelling errors, including `RabbbitPassword` and `ExternalAudioUrlParamPasshprase`; match them exactly. +Run the one-liner or `./setup.sh upgrade` as for any upgrade. Setup recognizes the old layout: it keeps the `.env` that older versions tracked in git across the `git pull`, renames the `NGINX_*` settings, switches the services to their new names and ports, and copies the data of installs up to April 2025 (`docker-data/sql`, PostgreSQL 17) into the new PostgreSQL 18 database, once and only while the new database is empty. The old folder isn't changed and a dump is kept in `backups/`. See [Upgrading an install created before October 2026](./features-and-upgrades#upgrading-an-install-created-before-october-2026). + +## Troubleshooting + +| Symptom | Cause and fix | +|---|---| +| Certificate warning in the browser | `internal` certificates: install `certs/ca/resgrid-root-ca.crt` on the device ([Certificates](./certificates)). Accepting the warning for the web hostname alone is not enough, because the web app also calls the API and events hostnames. | +| Login works but chat, maps or live updates don't | The API or events hostname doesn't resolve on that client or isn't trusted. Open `https:///health` and `https:///health/GetCurrent` from the same device. | +| Containers restart during the first minutes | Normal while PostgreSQL, RabbitMQ and the migrations come up; follow `docker compose logs -f worker`. | +| `port is already allocated` for 80 or 443 | Another web server (IIS, Apache, nginx) holds the port. Stop it or set `RESGRID_HTTP_PORT` / `RESGRID_HTTPS_PORT`. | +| `password authentication failed for user "resgrid"` | The Docker volumes are from an earlier install with a different `.env`. Restore that `.env`, or delete the old data with `docker compose down -v` (destroys it). | +| Sign Up sends you back to the login page | `RESGRID__SystemBehaviorConfig__AllowSignupWithoutBilling` isn't `true`, or `RESGRID__ApiConfig__BackendInternalApikey` is empty (run `./setup.sh upgrade`). | +| web, api or events exit mentioning `BypassSslChecks` | It must be `false`; the containers trust the local CA instead. `./setup.sh upgrade` fixes it. | +| Worker logs `transient_nonexcl_queues is deprecated` | RabbitMQ 4 is missing `rabbitmq/rabbitmq.conf`; use the repository's `docker-compose.yml`. | +| Worker logs `ConstraintExists is not supported by the connectionless processor` | `RESGRID__DataConfig__CoreConnectionString` is empty; set it to the same value as `ConnectionString`. | diff --git a/docs/self-hosted/laptop.md b/docs/self-hosted/laptop.md index e9f401db..955ece7a 100644 --- a/docs/self-hosted/laptop.md +++ b/docs/self-hosted/laptop.md @@ -1,138 +1,165 @@ --- sidebar_position: 3 +title: Windows laptop (offline) --- -# Windows laptop or desktop +# Resgrid on a Windows laptop (offline) -The `laptop` branch runs the current Resgrid Core stack on one Windows computer with Docker Desktop. Use it for local development, evaluation, training, or a single-computer field deployment. It is not a redundant or multi-user production architecture. +The `laptop` branch of [resgrid-setup](https://github.com/Resgrid/resgrid-setup/tree/laptop) runs the complete Resgrid system on **one Windows 10 or 11 computer** with Docker Desktop. After a one-time setup while online it works **with no network connection at all**. One person uses Resgrid in a browser on that computer: an incident commander's laptop at a disconnected incident, a backup dispatch position, a training classroom, or simply to evaluate Resgrid. -Default published ports bind to `127.0.0.1`, so other computers cannot connect unless you intentionally change the Compose bindings and Windows firewall rules. +How it differs from a server install: + +- Every port listens on `127.0.0.1` only: other computers and phones can't connect. For several users or phones on a local network use the [single server](./installation) or the [Incident Command Kit](./rick). +- The hostnames are `rg.localhost`, `rgapi.localhost` and `rgevents.localhost`. Chrome, Edge and Firefox always resolve `*.localhost` to the computer itself, so **no hosts-file edit and no administrator rights** are needed. +- HTTPS comes from a local certificate authority that setup creates and trusts for your Windows account. +- `resgrid.ps1` handles start/stop, backups, restores, upgrades and offline maps. ## Requirements -- 64-bit Windows 10 or Windows 11 with virtualization enabled. -- Docker Desktop using Linux containers and the WSL2 backend. -- PowerShell 7 or Windows PowerShell 5.1. -- Git for Windows. -- At least 4 CPU cores, 16 GB RAM, and 20 GB free storage. -- Administrator access once to edit the Windows hosts file. +- Windows 10 22H2 or Windows 11, 64-bit, with hardware virtualization enabled (check *Task Manager > Performance > CPU > Virtualization: Enabled*). +- [Docker Desktop](https://docs.docker.com/desktop/setup/install/windows-install/) using the WSL 2 backend and Linux containers (the defaults). Docker Desktop is free for personal use, education and small businesses; larger organizations need a subscription. +- 4 CPU cores, 8 GB RAM (16 GB recommended), 25 GB of free disk plus 5–20 GB for offline maps. +- Internet access during setup (about 6 GB of downloads). + +## Step 1: Install Docker Desktop -For a field computer, use encrypted storage and keep backups on separate encrypted removable media. There is no automatic failover if the computer or its storage is damaged. +1. Install Docker Desktop and restart Windows if asked. +2. Start Docker Desktop and wait until the bottom-left corner shows **Engine running**. +3. In **Settings > General** turn on **Start Docker Desktop when you sign in to your computer**. Resgrid then starts automatically with Windows. -## Install from PowerShell +## Step 2: Download Resgrid -Start Docker Desktop, then open PowerShell: +Open **PowerShell** (Start menu, type *PowerShell*): ```powershell -git clone --branch laptop https://github.com/Resgrid/resgrid-setup.git resgrid -Set-Location .\resgrid -Set-ExecutionPolicy -Scope Process Bypass -.\setup.ps1 +git clone --branch laptop https://github.com/Resgrid/resgrid-setup.git $HOME\resgrid +cd $HOME\resgrid ``` -`setup.ps1` checks Docker Desktop, generates unique infrastructure passwords and Resgrid encryption keys, creates new OpenIddict certificates with .NET, writes `.env`, creates data directories, and starts Compose. - -To configure without pulling or starting images: +Without git: download [the laptop branch zip](https://github.com/Resgrid/resgrid-setup/archive/refs/heads/laptop.zip), extract it to `C:\Users\\resgrid`, then in PowerShell: ```powershell -.\setup.ps1 -NoStart +cd $HOME\resgrid +Get-ChildItem -Recurse | Unblock-File ``` -Custom local hostnames: +## Step 3: Run setup ```powershell -.\setup.ps1 ` - -WebHost dispatch.mylocal ` - -ApiHost dispatchapi.mylocal ` - -EventsHost dispatchevents.mylocal +powershell -ExecutionPolicy Bypass -File .\setup.ps1 ``` -If you prefer WSL2, run `bash setup.sh` from the checkout; the script defaults to downloading the `laptop` branch. - -## Configure local name resolution - -Open Notepad as Administrator and edit: +To load an offline map of your area in the same step, add the [Geofabrik](https://download.geofabrik.de) extract that covers it: -```text -C:\Windows\System32\drivers\etc\hosts +```powershell +powershell -ExecutionPolicy Bypass -File .\setup.ps1 -MapRegion https://download.geofabrik.de/north-america/us/nevada-latest.osm.pbf ``` -Add the line printed by setup. With default names: +Setup: + +1. Checks that Docker Desktop is running Linux containers. +2. Picks the ports: 443 and 80, or 8443/8080 (or the next free ones) when IIS or another program already uses them. +3. Generates every password, encryption key and certificate into `.env`. +4. Creates the Resgrid local certificate authority and asks Windows to trust it for your account. **Windows shows a security warning asking whether to install a certificate from "Resgrid Local CA": choose Yes.** +5. Downloads the images, starts Resgrid and waits until it answers. +6. Walks you through the **optional features** (Records, Unified search, Chat, Run cards, ...): one question per feature, which turns on everything the feature needs ([Optional features and upgrades](./features-and-upgrades)). Then it opens Resgrid in your browser. ```text -127.0.0.1 rg.mylocal rgapi.mylocal rgevents.mylocal +==> Checking Docker Desktop +==> Generating passwords, keys and certificates +==> Creating the Resgrid local certificate authority (10 year validity) +==> Configuration written to .env. It holds all your secrets: back it up (.\resgrid.ps1 backup) and keep it private. +==> Trusting the Resgrid local CA for your Windows account (answer Yes in the Windows prompt) +==> Downloading container images (several GB the first time) +==> Starting Resgrid +==> Waiting for Resgrid to answer (the first start creates the database, 5-10 minutes) +.....==> Resgrid is up. + + Resgrid: https://rg.localhost + Choose 'Sign Up' to create the first account and department. ``` -Open these URLs and accept Caddy's internal certificate for each hostname: +Options: `-HttpsPort` / `-HttpPort` (choose ports), `-WebHost` / `-ApiHost` / `-EventsHost` (other hostnames; names not ending in `.localhost` need the hosts-file line setup prints), `-NoTrustCa` (don't change the Windows certificate store), `-Features records,search` (optional features without questions; `all` or `none`), `-NonInteractive`, `-NoPull`, `-NoStart`, `-NoBrowser`. -- `https://rg.mylocal` -- `https://rgapi.mylocal/api/health/getcurrent` -- `https://rgevents.mylocal` +If setup had to use port 8443, the address is `https://rg.localhost:8443`; `.\resgrid.ps1 status` always prints it. -The web client cannot use the API until the API certificate is accepted or Caddy's local root certificate is installed in Windows Trusted Root Certification Authorities. +## Step 4: Create your department -## Verify first start +In the browser choose **Create an account**, fill in your name, email, department name and type, user name and password, and choose **Register**. You are signed in as the department's administrator. -The worker creates and migrates the main, OIDC, worker, and document PostgreSQL databases: +![Resgrid dashboard running on the laptop](/img/self-hosted/laptop-dashboard.png) -```powershell -docker compose ps -docker compose logs -f worker -``` +Then set up the department ([setup guide](../configuration/setup)). Because the laptop works offline and sends no email, add people under **Personnel > Add Person** and set their passwords there. + +:::tip Firefox +Chrome and Edge trust the local CA right away. If Firefox still shows a certificate warning, open `about:config` and set `security.enterprise_roots.enabled` to `true`. +::: -When migrations settle, open `https://rg.mylocal` and use **Sign Up**. There is no shared default administrator login. +## Everyday use -Local diagnostic ports are: +Open PowerShell in the install folder (`cd $HOME\resgrid`): -| Service | Address | +| Command | What it does | |---|---| -| Web | `http://127.0.0.1:5151` | -| API | `http://127.0.0.1:5152` | -| Events | `http://127.0.0.1:5153` | -| TTS | `http://127.0.0.1:5154` | -| MCP | `http://127.0.0.1:5155/mcp` | -| RabbitMQ management | `http://127.0.0.1:5160` | +| `.\resgrid.ps1 open` | Open Resgrid in the browser | +| `.\resgrid.ps1 status` | Show every service and the Resgrid address | +| `.\resgrid.ps1 stop` / `start` | Stop / start (data is kept) | +| `.\resgrid.ps1 logs worker` | Follow a service's log (`web`, `api`, `events`, `worker`, `caddy`, …) | +| `.\resgrid.ps1 backup` | Database, `.env` and certificates into `backups\resgrid-.zip` | +| `.\resgrid.ps1 restore ` | Replace the data with a backup (asks you to type `RESTORE`) | +| `.\resgrid.ps1 maps-import ` | Load an offline map region | +| `.\resgrid.ps1 features` | Turn optional features (Records, Search, Chat, ...) on or off | +| `.\resgrid.ps1 upgrade` | Newest setup files, images and settings; repairs the install (needs internet) | +| `.\resgrid.ps1 trust-ca` | Trust the local CA again (e.g. for a new Windows user) | +| `.\resgrid.ps1 uninstall [-DeleteData]` | Remove the containers (and, with `-DeleteData`, all data) | -MCP's transport endpoint is unauthenticated; the loopback binding is intentional. +If PowerShell refuses to run the script, use `powershell -ExecutionPolicy Bypass -File .\resgrid.ps1 `. -## Optional services +## Offline maps -Tracker gateway: +The web map needs map tiles. Online it can use a tile provider; offline it uses the tile server that runs on the laptop. Import your area while you still have internet: ```powershell -docker compose --profile tracking up -d +.\resgrid.ps1 maps-import https://download.geofabrik.de/north-america/us/nevada-latest.osm.pbf ``` -Configure `RESGRID__UnitTrackingConfig__*` first. Tracker ports listen on network interfaces so physical devices can connect; review Windows firewall rules. +or a file you downloaded: `.\resgrid.ps1 maps-import C:\Downloads\nevada-latest.osm.pbf`. Choose the smallest extract that covers your area: a US state takes 10–60 minutes to import and 5–20 GB of disk. The command starts the tile server and points the web map at `https://rg.localhost/tiles/...`. -Inbound-email relay: +![Resgrid web map with tiles from the offline tile server](/img/self-hosted/offline-map.png) -```powershell -docker compose --profile relay up -d -``` +Tiles are drawn the first time an area is viewed and cached afterwards. Before you go offline, browse your area at the zoom levels you will use so they are ready. The tile server starts with Resgrid from then on (`maps` in `COMPOSE_PROFILES` in `.env`). -Configure the relay department and mail domains first. The SMTP listener has no authentication or TLS and opens port 25. +## What works offline -## Update +Everything Resgrid needs runs on the laptop. Without internet: -```powershell -docker compose pull -docker compose up -d -docker compose logs -f worker -``` +- **Works**: calls and dispatches, units, personnel, statuses and staffing, maps (with offline tiles), messages, logs, reports, shifts, calendar, documents, text-to-speech. +- **Doesn't**: email (invitations, password resets, notifications), push notifications, SMS and voice, address search (geocoding) and routing on the map, map tiles outside the imported area. Some pages use Google web fonts and fall back to Windows fonts. + +## Backups and moving to another laptop -Do not remove every Docker image; that affects unrelated local projects and is unnecessary. +`.\resgrid.ps1 backup` writes a zip with the database dump, `.env` (as `env.txt`) and the certificates. It contains every secret: keep it on encrypted removable storage, away from the laptop. -## Backup and rollback +To move Resgrid to another laptop: install Docker Desktop and download the laptop branch there, run `setup.ps1 -NoStart`, replace `.env` with `env.txt` from the backup and the `certs` folder with the backup's `certs`, then `.\resgrid.ps1 start` and `.\resgrid.ps1 restore `. -Back up `.env` and the `docker-data` directory to separate encrypted storage. `.env` contains encryption and OIDC material required by existing data. Create a consistent database dump: +## Upgrades + +With internet, after a backup: ```powershell -docker compose exec -T db pg_dumpall -U resgrid | Out-File -Encoding utf8 .\resgrid-backup.sql +.\resgrid.ps1 upgrade ``` -Before upgrading, record the current image digests and keep the pre-upgrade dump. Rolling back containers alone may be unsafe after worker schema migrations; restore the compatible database dump and matching `.env` before starting older images. +`upgrade` gets the newest setup files (`git pull`; without git, extract the newest laptop branch zip over the folder first) and runs `setup.ps1` again. Running `setup.ps1` again is always safe: it saves a copy of `.env`, adds the settings new versions need and repairs broken ones, keeps the data of older versions, downloads the new images, restarts (the database is migrated automatically) and asks only about optional features that are new. It never replaces a value you set or deletes data. Without internet, `.\setup.ps1 -NoPull` repairs and restarts with the images you have. Details: [Upgrade and repair](./features-and-upgrades#upgrade-and-repair). -## Mapping and offline use +## Troubleshooting -The default Leaflet setting uses the public OpenStreetMap tile service for initial testing only. It is not an offline map and is not intended for bulk or operational use. Configure an approved map provider or a separately prepared local tile service before deploying the laptop without internet access. +| Symptom | Fix | +|---|---| +| `Docker Desktop is not running` | Start Docker Desktop, wait for **Engine running**, run the command again. | +| `Docker Desktop is using Windows containers` | Right-click the Docker tray icon, **Switch to Linux containers**. | +| Certificate warning | `.\resgrid.ps1 trust-ca`, then restart the browser. Firefox: see the tip above. | +| Browser can't find `rg.localhost` | Use Chrome, Edge or Firefox. Other browsers may need hosts-file entries. | +| Sign Up returns to the login page | Run `.\resgrid.ps1 upgrade` (adds `RESGRID__SystemBehaviorConfig__AllowSignupWithoutBilling=true`). | +| `429 Too Many Requests` during setup | Docker Hub rate limit: sign in to Docker Desktop and run setup again. | +| `password authentication failed for user "resgrid"` | Data from an earlier install with a different `.env`. Restore that `.env`, or run `.\resgrid.ps1 uninstall -DeleteData` and set up again. | diff --git a/docs/self-hosted/multi.md b/docs/self-hosted/multi.md index 99784491..ce28d74b 100644 --- a/docs/self-hosted/multi.md +++ b/docs/self-hosted/multi.md @@ -1,159 +1,192 @@ --- sidebar_position: 5 +title: Kubernetes / k3s (multi-server) --- -# Kubernetes and k3s +# Resgrid on Kubernetes / k3s -The `multi` branch deploys current Resgrid Core services with Kustomize through `kubectl apply -k`. It does not require Helm. +The `multi` branch of [resgrid-setup](https://github.com/Resgrid/resgrid-setup/tree/multi) deploys Resgrid to a Kubernetes cluster spread over several servers. It is the production-grade layout: the stateless Resgrid services can run several replicas, nodes can be drained for maintenance without downtime, and the stateful services keep their data on persistent volumes. It needs only `kubectl` (its built-in Kustomize) and works out of the box on [k3s](https://k3s.io) and on any conformant cluster (kubeadm, RKE2, AKS, EKS, GKE). -The base creates: +Resgrid on Kubernetes: an Ingress routes to the web, api and events Deployments, which use PostgreSQL, Redis, RabbitMQ and RustFS StatefulSets with persistent volumes -- Web, API, events, worker/migrations, TTS, and MCP Deployments. -- PostgreSQL, Redis, RabbitMQ, and RustFS single-replica StatefulSets with persistent volume claims. -- A RustFS bucket initialization Job. -- A standard Kubernetes Ingress for the web, API/TTS, and events hostnames. -- A generated Secret containing the complete Resgrid environment. +| Kind | Name | Notes | +|---|---|---| +| Deployment | `web`, `api` | Stateless; scale freely. Session keys and the real-time backplane live in Redis. | +| Deployment | `events` | One replica unless your ingress supports sticky sessions (real-time clients negotiate and connect in two requests). | +| Deployment | `worker` | Exactly one. Its init container creates the four databases; the worker then creates/migrates the schema and runs scheduled jobs. | +| Deployment | `tts`, `mcp` | Text-to-speech; MCP for AI assistants (cluster-internal only, its endpoint is unauthenticated). | +| StatefulSet | `db`, `redis`, `rabbitmq`, `rustfs` | One replica each with a `ReadWriteOnce` volume (20, 2, 5, 20 GiB). | +| Job | `rustfs-init` | Creates the object storage buckets. | +| Ingress | `resgrid` | Web, API (plus `/tts/audio`) and events hostnames with TLS. | -Optional manifests add tracker-gateway and inbound-email relay workloads. +Every Resgrid pod runs as non-root on port 8080, has readiness probes, resource requests and limits, and trusts the Resgrid local CA (ConfigMap `resgrid-ca`). -:::warning Availability -The included StatefulSets are single replicas and are not an HA data tier. For production, use managed/clustered dependencies, image digests, resource policies, monitoring, network policies, and storage-provider backups appropriate to the workload. -::: +## Requirements -## Architecture requirement +- Kubernetes 1.27+ or k3s with **at least one `linux/amd64` node** (the published Resgrid images are amd64-only today). +- A default `ReadWriteOnce` StorageClass (k3s: `local-path`) and an IngressClass (k3s: `traefik`). +- A workstation with `kubectl` connected to the cluster, `openssl` and `bash` (Linux, macOS or WSL). +- DNS records for the web, API and events hostnames pointing at the ingress address (the k3s nodes, or your load balancer). +- Capacity: the base install requests about 2 vCPU and 3 GB of RAM. Three nodes with 4 vCPU / 8 GB each is a good production start. -Current Resgrid application images are published for `linux/amd64` only. Application Deployments select nodes labeled `kubernetes.io/arch=amd64`. Infrastructure can run on supported ARM64 nodes, so a mixed cluster works, but a pure Raspberry Pi/ARM64 k3s cluster cannot run the application pods natively today. +## Step 1: Build the cluster (k3s) -`setup.sh` refuses to apply when the cluster has no registered AMD64 node. Do not remove the node selector unless you publish and validate native ARM64 images or deliberately provide a supported emulation runtime. +Skip this if you already have a cluster. On the first server: -## Requirements +```bash +curl -sfL https://get.k3s.io | sh -s - server --cluster-init +sudo cat /var/lib/rancher/k3s/server/node-token +``` + +On two more servers, for a highly available control plane with embedded etcd: -- Kubernetes 1.27+ or a current k3s release. -- At least one AMD64 worker. -- A default `ReadWriteOnce` StorageClass (`local-path` is the k3s default). -- An IngressClass (`traefik` is the k3s default). -- `kubectl`, OpenSSL, and a configured kube context. -- Three DNS names pointing to the ingress address. -- Registry credentials if `dhi.io` requires authentication for your environment. +```bash +curl -sfL https://get.k3s.io | K3S_URL=https://:6443 K3S_TOKEN= sh -s - server +``` -Verify before installing: +Copy `/etc/rancher/k3s/k3s.yaml` from the first server to your workstation as `~/.kube/config`, replace `127.0.0.1` in it with the first server's address, and check: ```bash -kubectl config current-context -kubectl get nodes -L kubernetes.io/arch -kubectl get storageclass -kubectl get ingressclass +kubectl get nodes -o wide +kubectl get storageclass,ingressclass ``` -## Configure and apply +## Step 2: Configure and deploy ```bash git clone --branch multi https://github.com/Resgrid/resgrid-setup.git resgrid-k8s cd resgrid-k8s -chmod +x setup.sh ./setup.sh ``` -Setup asks for the three hostnames, IngressClass, and pod CIDR. It generates infrastructure credentials, Resgrid keys, OpenIddict certificates, ingress TLS files, and ignored host-specific inputs under `deploy/generated`. It displays the active kube context and requires confirmation before applying. +`setup.sh` asks for the three hostnames, the IngressClass (`traefik` on k3s) and the pod network (`10.42.0.0/16` on k3s), then: + +1. Writes `deploy/generated/resgrid.env` with every password, key and the OpenID Connect certificates. +2. Creates the Resgrid local CA and issues the ingress certificate for the three hostnames from it, unless you pass your own certificate with `RESGRID_TLS_CERT_FILE` / `RESGRID_TLS_KEY_FILE` (recommended for production). +3. Finds the ingress controller's cluster address and lets the pods reach the public hostnames through it (the web app gets API tokens from the API's public URL; events and MCP call it too). +4. Shows the `kubectl` context, asks you to type `yes` (once per context), applies everything and waits for the rollout. +5. Walks you through the **optional features** (Records, Unified search, Chat, Run cards, virus scanning, ...), one question per feature ([Optional features and upgrades](./features-and-upgrades)): + +```text +==> Pods will reach the public hostnames through the ingress at 10.43.110.230. +==> Validating manifests +Apply Resgrid to Kubernetes context 'default'? Type yes [no]: yes +==> Applying to context default +... +==> Waiting for the database migration and the web app (first install: 5-15 minutes) +deployment "worker" successfully rolled out +deployment "api" successfully rolled out +deployment "web" successfully rolled out +deployment "events" successfully rolled out +``` -Generate without changing the cluster: +Watch the first start: ```bash -RESGRID_NO_APPLY=1 ./setup.sh -kubectl kustomize deploy >/dev/null +kubectl -n resgrid get pods -w +kubectl -n resgrid logs -f deployment/worker ``` -Non-interactive install using an existing TLS certificate: +Unattended: ```bash -RESGRID_WEB_URL=dispatch.example.com \ -RESGRID_API_URL=dispatchapi.example.com \ -RESGRID_EVENTS_URL=dispatchevents.example.com \ -RESGRID_INGRESS_CLASS=nginx \ -RESGRID_POD_CIDR=10.244.0.0/16 \ -RESGRID_TLS_CERT_FILE=/secure/fullchain.pem \ -RESGRID_TLS_KEY_FILE=/secure/privkey.pem \ -RESGRID_APPLY=1 \ -./setup.sh +RESGRID_WEB_HOST=dispatch.example.com RESGRID_API_HOST=dispatchapi.example.com \ +RESGRID_EVENTS_HOST=dispatchevents.example.com RESGRID_INGRESS_CLASS=traefik \ +RESGRID_POD_CIDR=10.42.0.0/16 RESGRID_TLS_CERT_FILE=/secure/fullchain.pem \ +RESGRID_TLS_KEY_FILE=/secure/privkey.pem RESGRID_FEATURES=records,search RESGRID_APPLY=1 ./setup.sh ``` -Without supplied TLS files, setup generates a self-signed certificate containing all three hostnames. Distribute/trust `deploy/generated/tls.crt` on clients. Treat this as a lab or LAN-only option: for an internet-reachable cluster, supply a certificate issued by a trusted CA through `RESGRID_TLS_CERT_FILE` / `RESGRID_TLS_KEY_FILE` (or replace the ingress TLS secret with one managed by your cluster's certificate tooling) so browsers and mobile apps trust the web, API, and events endpoints without manual certificate installation. +`RESGRID_NO_APPLY=1 ./setup.sh` only writes the files; inspect the result with `kubectl kustomize deploy`. -The pod CIDR configures which ingress-to-pod forwarded headers Resgrid trusts. k3s commonly uses `10.42.0.0/16`; inspect your CNI and supply its actual network for other distributions. +:::caution +`deploy/generated/` holds every secret and the CA key. Back it up securely; it is not in git. +::: -## Registry login +## Step 3: Create your department -If image pulls from `dhi.io` require authentication: +Open `https://`, choose **Create an account** and register your department. Then turn self-service sign-up off: set `RESGRID__SystemBehaviorConfig__AllowSignupWithoutBilling=false` in `deploy/generated/resgrid.env` and run `./setup.sh`. -```bash -kubectl -n resgrid create secret docker-registry dhi-registry \ - --docker-server=dhi.io \ - --docker-username='YOUR_USER' \ - --docker-password='YOUR_TOKEN' -kubectl -n resgrid patch serviceaccount default \ - -p '{"imagePullSecrets":[{"name":"dhi-registry"}]}' -``` +![Resgrid calls page served from the k3s cluster](/img/self-hosted/k8s-calls.png) + +With the local CA, install `deploy/generated/ca.crt` on client devices ([Certificates](./certificates)). Android phones running the Resgrid apps need a publicly trusted certificate. -Use a service/robot token and keep it out of Git. +## Images and pull limits -## Verify first start +Pods pull `resgridllc/*:latest` (`imagePullPolicy: Always`, so a restart picks up new releases) and hardened infrastructure images from `dhi.io`. Docker Hub limits anonymous pulls; give the namespace credentials: ```bash -kubectl -n resgrid get pods -w -kubectl -n resgrid logs -f deployment/worker -kubectl -n resgrid get ingress -kubectl -n resgrid get pvc +kubectl -n resgrid create secret docker-registry regcred --docker-server=https://index.docker.io/v1/ --docker-username= --docker-password= +kubectl -n resgrid create secret docker-registry dhi --docker-server=dhi.io --docker-username= --docker-password= +kubectl -n resgrid patch serviceaccount default -p '{"imagePullSecrets":[{"name":"regcred"},{"name":"dhi"}]}' ``` -The worker creates/migrates the main, OIDC, worker, and document PostgreSQL databases. After migrations settle, open the web hostname and use **Sign Up**. +For change control in production, pin versions with an `images:` block in `deploy/kustomization.yaml`. -## Optional services - -Tracker gateway: update `RESGRID__UnitTrackingConfig__*` in `deploy/generated/resgrid.env`, re-apply the generated Secret/base, then create its workload: +## Operations ```bash -kubectl apply -k deploy -kubectl apply -f deploy/optional/tracking.yaml -kubectl -n resgrid get service tracker-gateway +kubectl -n resgrid get pods,pvc,ingress +kubectl -n resgrid logs -f deployment/api +kubectl -n resgrid scale deployment/web deployment/api --replicas=3 ``` -The LoadBalancer Service publishes tracker TCP/UDP ports. k3s ServiceLB and cloud providers advertise them differently; verify the assigned address and firewall exposure. +Change settings in `deploy/generated/resgrid.env`, then run `./setup.sh` (re-applies the Secret and restarts the deployments). + +**Optional features**: `./setup.sh features` turns Records, Unified search, Chat, Run cards, virus scanning and the other optional modules on or off ([Optional features and upgrades](./features-and-upgrades)). Flags are switched through `kubectl exec` into `db-0`; virus scanning applies `deploy/optional/clamav.yaml`. -Email relay: update its department/domain settings, re-apply, then: +**Upgrade and repair**: `./setup.sh upgrade` (git pull, then the same as `./setup.sh`). Re-running setup is always safe: it adds new settings and repairs broken ones in `resgrid.env`, recreates missing generated files, re-applies the manifests (the bucket job runs again, so new buckets exist), restarts StatefulSet pods stuck on an older definition, restarts the deployments so they pull the latest images (the worker migrates the database) and asks only about new optional features. It never replaces a value you set or deletes data. Back up first. + +**Backup**: ```bash -kubectl apply -k deploy -kubectl apply -f deploy/optional/relay.yaml -kubectl -n resgrid get service relay +day=$(date +%F) +kubectl -n resgrid exec statefulset/db -- sh -c 'pg_dumpall -U "$POSTGRES_USER" --clean --if-exists' > resgrid-$day.sql \ + && gzip -f resgrid-$day.sql ``` -The relay publishes SMTP port 25 without SMTP authentication or TLS. Place it behind a filtering MTA or strict firewall. +Compress only after `pg_dumpall` succeeds (as above) or run the pipeline under `set -o pipefail`: `pg_dumpall | gzip` returns `gzip`'s status, so a failed `kubectl exec` or dump otherwise leaves a `.sql.gz` that looks valid and is empty or truncated. -## Scaling and customization +Also keep `deploy/generated/` and snapshots of the `data-rustfs-0` volume. -Scale stateless services after validating dependency capacity: +**Restore** (database): ```bash -kubectl -n resgrid scale deployment/web deployment/api deployment/events --replicas=2 +day=2026-10-27 # the date of the backup you are restoring +kubectl -n resgrid get deploy -o jsonpath='{range .items[*]}{.metadata.name}{" "}{.spec.replicas}{"\n"}{end}' > replicas-$day.txt +kubectl -n resgrid scale deployment --all --replicas=0 +kubectl -n resgrid wait --for=delete pod -l 'app notin (db,redis,rabbitmq,rustfs)' --timeout=5m +gunzip -c resgrid-$day.sql.gz | kubectl -n resgrid exec -i statefulset/db -- sh -c 'psql -q -U "$POSTGRES_USER" -d postgres' +while read -r name replicas; do kubectl -n resgrid scale deployment "$name" --replicas="$replicas"; done < replicas-$day.txt ``` -Use a Kustomize overlay for image digests, storage sizes/classes, resources, topology constraints, probes, or external data services. Keep generated secrets out of overlays and Git. +`kubectl scale` only changes the desired replica count; the pods terminate afterward. Wait for the API, events and worker pods to be gone (the `wait` above, or `kubectl -n resgrid get pods` until only the StatefulSet pods are left) before loading the dump, or they write to the database while it is being replaced. Adjust the label selector to the labels your manifests set. -MCP is a ClusterIP-only Service because its HTTP transport is unauthenticated. Add an authenticated ingress only if your security design explicitly protects it. +Record the replica counts first and replay them (the `replicas-$day.txt` round trip above) rather than scaling a fixed list back to 1: `--all` stops the optional Deployments too, and a hard-coded `--replicas=1` would also undo scaling such as three `web` and `api` replicas. -## Backup and rollback +This replays the database only. For a full recovery also restore the `data-rustfs-0` snapshot from the same backup, with your cluster's snapshot or CSI tooling, while the `rustfs` StatefulSet is scaled to 0 — database rows point at the TTS audio and import objects in it, so a database-only restore leaves records referencing objects that no longer exist. The Unified search index needs no backup: the worker rebuilds it. -Create a PostgreSQL dump: +**Certificate renewal** (local CA): `./setup.sh renew-cert` re-issues the ingress certificate (valid 397 days). -```bash -kubectl -n resgrid exec statefulset/postgres -- \ - pg_dumpall -U resgrid > "resgrid-$(date +%F).sql" -``` +## Production hardening -Back up `deploy/generated/resgrid.env`, ingress TLS material, and RustFS/PostgreSQL PVCs using your storage provider's consistent snapshot/backup system. +The base keeps one replica of each stateful service so it runs on any cluster. For high availability: + +- **PostgreSQL**: [CloudNativePG](https://cloudnative-pg.io) or a managed database with replicas and scheduled backups. Point the five `*ConnectionString` settings at it and remove the `db` StatefulSet. +- **RabbitMQ**: the RabbitMQ Cluster Operator with three nodes (`RabbitHostname`, `RabbitHostname2`, `RabbitHostname3`); keep `deprecated_features.permit.transient_nonexcl_queues = true` in its configuration. +- **Redis**: a managed Redis or Sentinel. +- **Object storage**: RustFS in distributed mode or any S3 service. +- Spread `web` and `api` replicas over nodes, add PodDisruptionBudgets, and use replicated storage (Longhorn, Ceph, cloud volumes). + +## Optional services -Before upgrading, record image digests and render the candidate with `kubectl kustomize deploy`. Apply with `kubectl apply -k deploy`, then follow worker migrations and rollouts. +Optional services are the files in `deploy/optional/` listed in `RESGRID_OPTIONAL` (comma separated) in `deploy/generated/resgrid.env`; `./setup.sh` applies them on every run and removes the ones it applied before that you took off the list. `clamav` is managed by the virus-scanning feature. For GPS trackers or inbound email dispatch, enable their settings, add `tracking` or `relay` to `RESGRID_OPTIONAL` and run `./setup.sh`. Both create `LoadBalancer` services. The relay needs its department (`RESGRID__RELAY__Resgrid__DepartmentId`) and dispatch domains (`RESGRID__RELAY__Smtp__DepartmentAddressDomains__0`) set, and the domains' MX records pointed at the service: it only accepts recipients in those domains. It is not an open mail relay, but it accepts any sender and offers no TLS or SMTP authentication, so restrict port 25 to the senders you expect or put a filtering MTA that terminates TLS in front of it before exposing it to the internet. -Application rollback does not reverse database migrations. Retain previous manifests/digests plus a compatible pre-upgrade database backup. Restore data and the matching generated Secret before reapplying older workloads when schema compatibility requires it. +## Troubleshooting -`kubectl delete -k deploy` removes the namespace workloads and generated resources; StatefulSet/PVC retention behavior varies. Inventory and back up PVCs before any uninstall. +| Symptom | Fix | +|---|---| +| Pods stuck in `ErrImagePull` with `429 Too Many Requests` | Docker Hub rate limit: add pull credentials (above). | +| `No amd64 node found` | Add an amd64 node; the Resgrid images are amd64-only today. | +| Web loads but login or live updates fail | The API or events hostname doesn't resolve or isn't trusted on the client. Inside the cluster, check `kubectl -n resgrid get pod -l app=web -o jsonpath='{.items[0].spec.hostAliases}'`. | +| Worker init container fails | PostgreSQL isn't ready yet; it retries. Check `kubectl -n resgrid logs deploy/worker -c create-databases`. | +| `kubectl delete -k deploy` deleted the data | Most storage classes delete volumes with the namespace. Restore from backup. | diff --git a/docs/self-hosted/quick-start.md b/docs/self-hosted/quick-start.md index 909b32a2..0f7d392a 100644 --- a/docs/self-hosted/quick-start.md +++ b/docs/self-hosted/quick-start.md @@ -1,92 +1,67 @@ --- sidebar_position: 1 +title: Quick start --- # Quick start -This quick start runs the current Resgrid Core stack on one 64-bit Linux server for evaluation or a small self-hosted environment. For Windows, Raspberry Pi RICK, and Kubernetes/k3s layouts, start with the [deployment comparison](./installation). +Run your own Resgrid server on one Linux machine in about 15 minutes. For a Windows laptop, a Kubernetes cluster or the Raspberry Pi Incident Command Kit, start from the [deployment comparison](./installation) instead. :::danger Evaluation is not production readiness -The stack has single instances of its database, cache, message bus, and object store. Production use requires security review, monitoring, capacity planning, tested backups, and a recovery plan. Never deploy the committed template credentials. +The single-server stack has one instance of its database, cache, message bus and object store. Production use needs a security review, monitoring, capacity planning, tested backups and a recovery plan. ::: -## 1. Prepare the host +## 1. Prepare a server -Install Docker Engine, Docker Compose v2, OpenSSL, and curl on a 64-bit Linux server. Ubuntu Server 24.04 LTS is a practical baseline. Start Docker and verify: +A 64-bit Linux server (Ubuntu Server 24.04 LTS works well) with 4 CPU cores, 8 GB RAM and 40 GB of disk. Install Docker: ```bash -docker info -docker compose version -openssl version +curl -fsSL https://get.docker.com | sudo sh +sudo usermod -aG docker $USER # then log out and back in ``` -Plan three hostnames that all resolve to the server: +Decide on three hostnames that point at the server: web (`dispatch.example.com`), API (`dispatchapi.example.com`) and events (`dispatchevents.example.com`). For a LAN-only test you can keep the defaults (`rg.mylocal`, `rgapi.mylocal`, `rgevents.mylocal`). -- Web, for example `dispatch.example.com` -- API, for example `dispatchapi.example.com` -- Events, for example `dispatchevents.example.com` - -For an internet deployment, forward TCP 80/443 and create real DNS records. For a LAN-only test, the installer defaults to `.mylocal` names and Caddy's internal certificate. - -The infrastructure images come from `dhi.io`. If image pulls report an authorization error, authenticate with an entitled Docker account: - -```bash -docker login dhi.io -``` - -## 2. Run the installer +## 2. Install ```bash curl -fsSL https://raw.githubusercontent.com/Resgrid/resgrid-setup/master/setup.sh | bash ``` -The installer asks for hostnames/TLS mode, generates unique passwords and Resgrid encryption keys, creates new OpenIddict certificates, writes `.env`, and starts the stack. - -Automated example: +Answer the hostname questions. For the certificate question give an email address (internet-facing server, Let's Encrypt) or press Enter for `internal` (LAN or offline). The installer generates every secret, starts Resgrid and waits until it answers. The first start takes 5–10 minutes while the database is created. -```bash -RESGRID_WEB_URL=dispatch.example.com \ -RESGRID_API_URL=dispatchapi.example.com \ -RESGRID_EVENTS_URL=dispatchevents.example.com \ -RESGRID_LETSENCRYPT_EMAIL=admin@example.com \ -bash setup.sh -``` +## 3. Trust the certificate (internal only) -Use `RESGRID_NO_START=1` to configure without pulling or starting containers. +With `internal` certificates, install `~/resgrid/certs/ca/resgrid-root-ca.crt` as a trusted root on every computer and phone that uses Resgrid, and add the hostnames to their hosts file if you have no DNS: -## 3. Verify migrations - -```bash -cd ~/resgrid -docker compose ps -docker compose logs -f worker +```text +192.168.1.50 rg.mylocal rgapi.mylocal rgevents.mylocal ``` -The worker creates and migrates four PostgreSQL databases. First start can take several minutes. +Step-by-step per operating system: [Certificates](./certificates). -With internal TLS, add all three hostnames to every client's hosts file and accept/trust the certificate for the web, API, and events endpoints. Accepting only the web certificate is insufficient. +:::caution Android apps need a public certificate +Installing the local CA on an Android phone only makes its browsers trust the server. The Resgrid **Responder** and **Unit** Android apps ignore user-installed CAs, so they need a publicly trusted certificate: use Let's Encrypt or your own public certificate if Android phones must use the apps. Android browsers and iPhones work with the local CA. +::: -When migrations settle, open the web hostname and use **Sign Up** to create the first account and department. There is no shared default administrator account. +## 4. Sign up -## 4. Operate and update +Open `https://`, choose **Create an account**, and fill in the form. This creates your department and makes you its administrator. -```bash -docker compose ps -docker compose logs -f SERVICE -docker compose pull -docker compose up -d -``` +![Sign up form](/img/self-hosted/sign-up.png) -Do not delete every Docker image during an update. Pulling and recreating this Compose project is sufficient and does not disturb unrelated containers. +Then turn self-service sign-up off (`RESGRID__SystemBehaviorConfig__AllowSignupWithoutBilling=false` in `~/resgrid/.env`, then `docker compose up -d`), set up your department ([setup guide](../configuration/setup)) and [connect the mobile apps](./apps). -Before an update, securely back up `.env` and create a consistent PostgreSQL dump: +## 5. Keep it running ```bash -docker compose exec -T db pg_dumpall -U resgrid > "resgrid-$(date +%F).sql" +cd ~/resgrid +docker compose ps # status +docker compose logs -f worker # logs +./setup.sh features # turn optional features (Records, Search, Chat, ...) on or off +./setup.sh upgrade # upgrade and repair (always safe to run) ``` -Keep the previous image digests and pre-update dump. Older application images may not work with a newly migrated schema, so rollback can require restoring PostgreSQL as well as the previous manifests/images. - -Continue with the full [self-hosted installation guide](./installation) for optional services, endpoints, configuration, backup scope, and rollback details. +Back up `~/resgrid/.env`, `~/resgrid/certs/` and a database dump regularly; the [installation guide](./installation#backups) has the commands, plus optional offline maps, GPS trackers and email dispatch. [Optional features and upgrades](./features-and-upgrades) explains the feature menu and what an upgrade does. diff --git a/docs/self-hosted/rick.md b/docs/self-hosted/rick.md index f0c9208b..fedfeb13 100644 --- a/docs/self-hosted/rick.md +++ b/docs/self-hosted/rick.md @@ -1,174 +1,293 @@ --- sidebar_position: 4 +title: Incident Command Kit (RICK) --- # Resgrid Incident Command Kit (RICK) -The `rick` branch divides Resgrid across three portable Linux nodes on a private wired LAN: +RICK is a portable Resgrid server you take to an incident: four Raspberry Pi 5 computers in a 1U rack, a network switch and a WiFi hotspot router, running on battery or vehicle power **without any internet connection**. -| Role | Services | +- **Responders** join the kit's WiFi and use the Resgrid **Responder** and **Unit** apps on their phones and tablets, connected to the kit instead of resgrid.com. +- The **incident commander or dispatcher** uses the Resgrid web app in a browser on a laptop connected to the kit. +- Everything — database, messaging, real-time updates, maps, text-to-speech — runs on the four Pis. Power the kit on and it starts by itself. + +The kit lives in the `rick` branch of [resgrid-setup](https://github.com/Resgrid/resgrid-setup/tree/rick). + +## How it works + +RICK network: phones and a laptop on the hotspot WiFi connect to rick1; rick1 forwards to rick2, rick3 and rick4 over the kit switch + +| Node | Address | Role | Runs | +|---|---|---|---| +| rick1 | 192.168.50.11 | **edge** | Caddy (HTTPS for every client), web app, events hub, optional kit DNS, time source | +| rick2 | 192.168.50.12 | **app** | API, worker (database migrations and background jobs), text-to-speech, MCP | +| rick3 | 192.168.50.13 | **data** | PostgreSQL | +| rick4 | 192.168.50.14 | **infra** | Redis, RabbitMQ, RustFS object storage, offline map tiles | + +Phones and laptops only ever talk to **rick1** on port 443, using three hostnames (`rg.rick.internal`, `rgapi.rick.internal`, `rgevents.rick.internal`) that the hotspot router resolves to rick1. Caddy on rick1 forwards API and audio requests to rick2 and map tiles to rick4. Splitting the roles spreads memory, CPU and disk I/O across four boards and keeps the database alone on its SSD. + +Every container restarts by itself and waits for the services on the other nodes, so after a power cut the kit comes back on its own, whatever order the Pis boot in. + +## Shopping list + +| Item | Notes | |---|---| -| Web/API | Caddy, web, API, events, worker/migrations, TTS, MCP; optional tracker gateway and relay | -| Infrastructure | Redis, RabbitMQ, RustFS; optional offline OSM tile service | -| Database | PostgreSQL and all four Resgrid databases | +| 4 × Raspberry Pi 5, **8 GB** | 16 GB gives more headroom on rick2 | +| UCTRONICS Pi 5 Rack Pro (1U, 4 slots) | An M.2 NVMe base, safe-shutdown button and status display per Pi | +| 4 × M.2 NVMe SSD, 128 GB or larger | Boot each Pi from its SSD; far more reliable than microSD cards | +| 4 × Raspberry Pi RTC battery | Keeps each Pi's clock when unpowered (rick1 at minimum) | +| Gigabit switch, 5+ ports | A PoE+ (802.3at) switch with ~120 W budget plus a PoE+ HAT per Pi replaces four power supplies; otherwise 4 × official 27 W USB-C supplies | +| WiFi travel router (e.g. GL.iNet Beryl AX / Slate AX) | DHCP, custom DNS records, WPA2/WPA3; no internet uplink needed in the field | +| Power | A power station (≥500 Wh) or vehicle power. The kit uses about 25–45 W: roughly 10–15 hours per 500 Wh. Add a small UPS on vehicle power. | +| Short Ethernet cables, an Ethernet cable for the laptop | | -This separation reduces resource contention and makes role recovery clearer, but it is not high availability. Every node is a single point of failure. +## Before you deploy: Raspberry Pi images -## Important architecture notice +RICK needs `linux/arm64` images. The infrastructure images (PostgreSQL, Redis, RabbitMQ, Caddy, RustFS, tile server) are multi-architecture. The Resgrid application images (`resgridllc/*`) are published for `linux/amd64` only today; Resgrid Core contains the Dockerfiles to build them for `linux/arm64` too. -The current published Resgrid application images are `linux/amd64` only. Infrastructure images publish native ARM64 variants, but the web-role Resgrid images do not. +Until multi-architecture images are on Docker Hub, build them once from a [Resgrid Core](https://github.com/Resgrid/Core) checkout on any computer with Docker, push them to a registry you can reach while preparing the kit, and set `RESGRID_IMAGE_REPO` (and `RESGRID_IMAGE_TAG`) in the kit's `.env`. The [rick README](https://github.com/Resgrid/resgrid-setup/tree/rick#images-for-raspberry-pi) has the build commands. Once `resgridllc` publishes multi-architecture images, keep the default `RESGRID_IMAGE_REPO=resgridllc`. -On a Raspberry Pi web node, RICK therefore runs the application images using the kernel's x86_64 binfmt/QEMU handler. This has a performance cost. Test the full kit under realistic call, unit, event, mapping, TTS, and tracker load before field use. Native multi-architecture Resgrid images are the preferred long-term solution. +## Step 1: Set up the router -Install persistent emulation support on the web Pi: +1. Set the router's LAN to `192.168.50.1/24` with a DHCP range such as `192.168.50.100–200`, and reserve `.11`–`.14` for the Pis. +2. Set the WiFi name and a strong WPA2/WPA3 password. This is the network responders join. +3. Add three DNS records pointing at rick1. On GL.iNet routers: **Network > DNS > Edit Hosts**; on OpenWrt: **Network > DHCP and DNS > Hostnames**. -```bash -sudo apt update -sudo apt install -y qemu-user-static binfmt-support -cat /proc/sys/fs/binfmt_misc/qemu-x86_64 -``` + ```text + 192.168.50.11 rg.rick.internal + 192.168.50.11 rgapi.rick.internal + 192.168.50.11 rgevents.rick.internal + ``` -The handler must report `enabled`. `rick.sh web start` stops with an actionable error if an ARM64 node lacks the handler. + If your router can't do this, run the kit's DNS server on rick1 instead (Step 6) and set the router's DHCP **DNS server** option to `192.168.50.11`. -## Hardware and LAN +4. Connect the router's LAN port and the four Pis to the switch. Plug the router's WAN port into internet only while preparing or updating the kit. -- Three Raspberry Pi 5 systems with 8 GB RAM recommended, or equivalent 64-bit Linux nodes. -- 64-bit Raspberry Pi OS/Ubuntu Server; 32-bit operating systems are unsupported. -- SSD storage for PostgreSQL; high-endurance storage for every node. -- Wired Ethernet switch and fixed DHCP reservations/static addresses. -- Docker Engine and Compose v2 on each node. -- Separate encrypted backup media. +:::caution Don't use .local names +iPhones and Macs look up `.local` names only with multicast DNS and ignore router records for them. Use `rick.internal` (reserved for private networks) or a subdomain you own. +::: -Example plan: +## Step 2: Prepare each Raspberry Pi (with internet) -| Role | Address | -|---|---| -| Web/API | `192.168.50.11` | -| Infrastructure | `192.168.50.12` | -| Database | `192.168.50.13` | +1. With Raspberry Pi Imager, write **Raspberry Pi OS Lite (64-bit)** to each SSD. In the imager's settings set the hostname (`rick1` … `rick4`), a user and password, and enable SSH. +2. Fit the SSDs and RTC batteries, slide the Pis into the rack, connect them to the switch and power them up. +3. SSH to each Pi (`ssh @rick1.lan` or its DHCP address) and run, with that node's name and address: + + ```bash + sudo git clone --branch rick https://github.com/Resgrid/resgrid-setup.git /opt/resgrid-rick + sudo chown -R $USER /opt/resgrid-rick + cd /opt/resgrid-rick + sudo ./os/prepare-node.sh rick1 192.168.50.11 192.168.50.1 + sudo reboot + ``` -All three client hostnames (`rg.rick.local`, `rgapi.rick.local`, and `rgevents.rick.local`) resolve to the web node. + `prepare-node.sh` sets the hostname and fixed address, installs Docker, configures time keeping (rick1 serves time to the others) and a Redis setting. Add `RICK_PCIE_GEN3=1` before `sudo` to enable faster PCIe Gen 3 for the SSD. -## Configure once +4. On rick1, create an SSH key and copy it to the other Pis so rick1 can manage the kit: -Clone on an administration computer or one node: + ```bash + ssh-keygen -t ed25519 + ssh-copy-id @192.168.50.12 + ssh-copy-id @192.168.50.13 + ssh-copy-id @192.168.50.14 + ``` + +## Step 3: Configure the kit (once, on rick1) ```bash -git clone --branch rick https://github.com/Resgrid/resgrid-setup.git resgrid-rick -cd resgrid-rick -chmod +x setup.sh rick.sh database/db/*.sh +cd /opt/resgrid-rick ./setup.sh ``` -The configurator asks for the three hostnames and three node addresses, then generates shared credentials, application keys, and OpenIddict certificates. Run it once only. Independently generated `.env` files will not interoperate. +```text +rick1 (edge) address [192.168.50.11]: +rick2 (app) address [192.168.50.12]: +rick3 (data) address [192.168.50.13]: +rick4 (infra) address [192.168.50.14]: +Hotspot router address [192.168.50.1]: +Kit domain [rick.internal]: + +==> Kit LAN 192.168.50.0/24, router 192.168.50.1 +==> Web https://rg.rick.internal API https://rgapi.rick.internal Events https://rgevents.rick.internal (all on 192.168.50.11) +==> Generating passwords, keys and certificates +==> Creating the Resgrid local certificate authority (10 year validity) +``` -Securely copy the complete configured checkout to the same path on each node: +Setup writes `.env` (every password and key for the whole kit), the local certificate authority in `certs/` and the kit DNS configuration. Copy the configured checkout to the other nodes: ```bash -rsync -a --delete ./ pi@192.168.50.11:/opt/resgrid-rick/ -rsync -a --delete ./ pi@192.168.50.12:/opt/resgrid-rick/ -rsync -a --delete ./ pi@192.168.50.13:/opt/resgrid-rick/ +./rick.sh sync ``` -Protect and back up `.env`; it contains database credentials, encryption keys, and OIDC private keys. +All four nodes must have the same `.env` and `certs/`. Run `setup.sh` on rick1 only; `./rick.sh sync` copies the result to the others and never touches their `docker-data/`, `backups/` or `bundle/` folders. At the end of the first run setup offers to start the kit straight away; answer `n` until the images and maps are ready (steps 4 and 5). -## Validate and start +## Step 4: Download the images (with internet) + +```bash +./rick.sh cluster pull +``` -Database node: +To be able to rebuild a node in the field without internet, also save all images to a file and keep it with the kit: `./rick.sh bundle` (on any computer with Docker and the configured checkout), then `./rick.sh load ` on a node. + +## Step 5: Load the offline map (with internet) + +On rick4, import the smallest [Geofabrik extract](https://download.geofabrik.de) that covers your response area: ```bash -cd /opt/resgrid-rick -./rick.sh database config -./rick.sh database start -./rick.sh database status +./rick.sh maps-import https://download.geofabrik.de/north-america/us/nevada-latest.osm.pbf ``` -Infrastructure node: +A US state takes 1–3 hours on a Pi 5 (faster on a PC: import with the single-server stack and copy the volume). Tiles render the first time they are viewed and are cached; browse your area at the zoom levels you use once before deploying. + +## Step 6: Start the kit + +From rick1: ```bash -cd /opt/resgrid-rick -./rick.sh infra config -./rick.sh infra start -./rick.sh infra status +./rick.sh rollout +``` + +`rollout` copies the checkout to every node, pulls the images on each (skipped when offline, or with `RESGRID_NO_PULL=1`), starts the nodes in order (data, infra, app, edge) over SSH, waits until the database is ready, walks you through the **optional features** and ends with a health check. The first start takes 10–20 minutes while rick2 creates the database. + +```text +==> Optional features + Resgrid ships some modules switched off. Answer y to turn a feature on for + every department on this server, n to turn it off; Enter takes the answer + in brackets. Run this again any time with: ./rick.sh features + + [1/10] Records (RMS) (now: off) + Incident and field records (replaces Logs), records in the Responder, Unit and Dispatch apps, + prevention (occupancy, inspections, hydrants, permits, community risk reduction), + investigations, quality review and records analytics. + After switching it on, a department administrator activates Records in the department settings. + Turn on Records (RMS)? (y/n) [n]: y +... +==> Records (RMS): on ``` -Web node, after database and infrastructure are healthy: +The features (Records, Unified search, Chat, Run cards, virus scanning on rick4, ...) are explained in [Optional features and upgrades](./features-and-upgrades); change them later with `./rick.sh features` on rick1. + +To start the kit again later: `./rick.sh cluster start`. On a single node, `./rick.sh start` starts that node's role (it recognises the role from the node's IP address; `./rick.sh role` shows it). Optional services listed in `COMPOSE_PROFILES` in `.env`, such as `maps` on rick4 (added by the map import) and `dns` on rick1, start with it. ```bash -cd /opt/resgrid-rick -./rick.sh web config -./rick.sh web start -./rick.sh web logs worker +./rick.sh health ``` -The worker migrates `resgrid`, `resgridoidc`, `resgridworkers`, and `resgriddoc`. Accept Caddy's internal certificate for all three client hostnames, then open the web hostname and use **Sign Up**. +```text +Kit health (from rick1): + rick3 data PostgreSQL 192.168.50.13:5432 ok + rick4 infra Redis 192.168.50.14:6379 ok + rick4 infra RabbitMQ 192.168.50.14:5672 ok + rick4 infra RustFS 192.168.50.14:9000 ok + rick2 app API 192.168.50.12:5152 ok + rick2 app TTS 192.168.50.12:5154 ok + rick1 edge web https://rg.rick.internal ok + rick1 edge API https://rgapi.rick.internal ok + rick1 edge events https://rgevents.rick.internal ok + rick4 infra map tiles 192.168.50.14:5156 ok +``` -## Network policy +## Step 7: Set up the command laptop -Permit only: +1. Join the kit WiFi or plug into the switch. +2. Copy the kit's CA certificate to the laptop, for example `scp @192.168.50.11:/opt/resgrid-rick/certs/ca/resgrid-root-ca.crt .`, and install it as a trusted root ([Certificates](./certificates#installing-the-ca-certificate)). +3. Open `https://rg.rick.internal`. The login page works without internet: -| Source | Destination | Ports | -|---|---|---| -| Clients | Web node | TCP 80/443 | -| Web node | Database node | TCP 5432 | -| Web node | Infrastructure node | TCP 5672, 6379, 9000 | -| Administrators | Infrastructure node | TCP 15672 if the RabbitMQ UI is needed | + ![Resgrid login page served by the kit with no internet](/img/self-hosted/rick-login-offline.png) + +4. Choose **Create an account** to create your department, then add personnel, units, stations and roles ([department setup](../configuration/setup)). The kit sends no email, so set each person's password under **Personnel > Add Person**. +5. Turn sign-up off: set `RESGRID__SystemBehaviorConfig__AllowSignupWithoutBilling=false` in `.env`, then `./rick.sh sync` and `./rick.sh cluster start`. + +Dispatching works exactly as online; calls, statuses and locations update live on every connected screen: + +![Calls page on the kit, with no internet](/img/self-hosted/rick-calls-offline.png) + +## Step 8: Connect responders' phones -Do not expose PostgreSQL, Redis, RabbitMQ, or RustFS outside the kit LAN. MCP is loopback-only on the web node. +Install the **Resgrid Responder** and **Resgrid Unit** apps **before** the phones go offline. Then on each phone: -## Prepare offline images +1. Join the kit WiFi. On Android choose **Stay connected** when it reports no internet, and turn off mobile data. On iPhone allow **Local Network** access when the app asks. +2. On the login screen tap **Change Server URL** (Responder) or **Server URL** (Unit), choose **Custom** and enter `https://rgapi.rick.internal`. The app can take up to 30 seconds to give up on resgrid.com first. +3. Log in with the user name and password from the web app. -While internet is available, run `./rick.sh ROLE pull` on the corresponding node. For air-gapped recovery, export the images reported by `docker compose config --images`, copy the archive to protected media, and test a restore with `docker load` before deployment. +Details and troubleshooting: [Connecting the mobile apps](./apps). -The web-node archive contains AMD64 images even though the host is ARM64. Preserve the binfmt packages and test after operating-system upgrades. +| Phone | Kit with the local CA (default) | Kit with a public certificate | +|---|---|---| +| iPhone / iPad | Install `resgrid-root-ca.crt` as a profile and enable it in **Settings > General > About > Certificate Trust Settings** | Works as is | +| Android | Browsers work after installing the CA; the **Responder and Unit apps don't trust user-installed CAs** | Works as is | + +What works on phones without internet: dispatches, calls, statuses, staffing, locations, messages and chat **while the app is open**. Not available: push notifications when the app is closed, maps in the apps (Mapbox), address search, voice. Ask responders to keep the app open with **Keep Alive** turned on in the app settings. -## Optional offline maps +## Publicly trusted certificate (Android apps) -Import a small regional `.osm.pbf` on the infrastructure node before the incident. Large imports can take many hours or days: +Android's Resgrid apps only accept certificates from public certificate authorities. Give the kit a domain you own (e.g. `rick.yourdept.org` as the kit domain in `setup.sh`) and get a Let's Encrypt certificate with a DNS challenge. This works even though the kit is never reachable from the internet: only a temporary DNS record is created at your DNS provider. On rick1, with internet: ```bash -mkdir -p docker-data/osm -docker run --rm \ - -v /path/to/region.osm.pbf:/data/region.osm.pbf:ro \ - -v "$PWD/docker-data/osm:/data/database/" \ - overv/openstreetmap-tile-server:latest import -./rick.sh infra start --profile maps +mkdir -p certs/acme +echo 'CF_DNS_API_TOKEN=your-token' > certs/acme/dns.env +RICK_DNS_PROVIDER=cloudflare RICK_ACME_EMAIL=you@yourdept.org ./rick.sh cert +./rick.sh sync && ./rick.sh start && ./rick.sh restart caddy ``` -Set `RESGRID__MappingConfig__LeafletTileUrl` in the shared `.env` to `http://INFRA_NODE:5156/tile/{z}/{x}/{y}.png`, redistribute `.env`, and recreate the web role. +Provider codes and credential names for Cloudflare, Route 53, Azure DNS, Google Cloud DNS and 100+ others: [lego DNS providers](https://go-acme.github.io/lego/dns/). The kit router keeps resolving the three hostnames to rick1. + +:::warning Renew every 60 days +Let's Encrypt certificates expire after 90 days. Run `./rick.sh cert` again while the kit is online at least every 60 days (it renews), or phones and browsers will refuse to connect. +::: -## Optional tracker and email services +## Time -After configuring tracking settings: +Certificates and logins depend on correct clocks. rick1 serves time to the other nodes; with its RTC battery it keeps time while powered off. Check with `chronyc tracking` on any node, and make sure phones and laptops show the right time. + +## Running an incident ```bash -./rick.sh web start --profile tracking +./rick.sh health # whole kit, from any node +./rick.sh status # this node's services +./rick.sh logs api # a service's log, on the node that runs it +./rick.sh cluster stop # before powering off (edge, app, infra, data) ``` -After configuring relay department/domain settings: +Shut down cleanly when you can: `./rick.sh cluster stop`, then `sudo poweroff` on each Pi or the rack's safe-shutdown buttons. + +After each deployment, back up on rick3 and copy the file off the kit to an encrypted USB drive: ```bash -./rick.sh web start --profile relay +./rick.sh backup # backups/resgrid-.tar.gz: database, .env, certificates ``` -Tracker ports and SMTP port 25 require deliberate firewall rules. The relay has no SMTP authentication or TLS. - -## Backup, update, and rollback +## Upgrades (with internet) -Database dump from the database node: +Back up first, then on rick1: ```bash -docker compose --env-file .env -p resgrid-rick-database -f database/docker-compose.yml \ - exec -T db pg_dumpall -U resgrid | gzip > "resgrid-$(date +%F).sql.gz" +./rick.sh upgrade ``` -Back up the dump, `.env`, RustFS data, and Caddy state off-kit. Validate recovery on spare media/nodes. +`upgrade` gets the newest setup files (`git pull`) and runs `./setup.sh` again, which then runs `./rick.sh rollout`. Re-running setup is always safe: it backs up `.env`, adds the settings new versions need (with your node addresses), repairs missing or broken values and files, and `rollout` copies everything to the nodes, pulls the new images, restarts the kit (the worker migrates the database), asks only about new optional features and re-applies yours. It never replaces a value you set or deletes data. Offline, `RESGRID_NO_PULL=1 ./setup.sh` repairs the configuration and restarts with the images on the nodes. -Update in database/infrastructure → web order, following worker migration logs. Retain previous image digests and a pre-upgrade dump. If migrations are incompatible with the previous release, restore PostgreSQL and `.env` before restarting the older web-role images. +Kits configured before October 2026 (three nodes: web, infra, database) upgrade the same way. Setup maps the old settings onto the four roles (the old web node runs edge and app), and `rollout` copies the old database into the data node once, while the new database is empty, then stops the old containers without removing them. + +If the old checkout has no `rick.sh upgrade` yet: `cp .env ~/rick-env && git checkout -- .env && git pull && cp ~/rick-env .env && ./setup.sh`. Details: [Upgrade and repair](./features-and-upgrades#upgrade-and-repair). + +Check the copied database before you rely on it: the rollout marks the migration done (`.legacy-db-migrated` on the data node) and carries on even when loading the dump reported errors, and later rollouts then skip it. Sign in and confirm your departments, personnel, units and calls are there. If they are not, the dump of the old database is kept in `backups/legacy-db-.sql.gz` with its load errors in `backups/legacy-db-.sql.gz.log` on the data node: fix what the log reports, delete `.legacy-db-migrated`, and run `./rick.sh rollout` again. The old database container is only stopped, never removed, so it is still there as a fallback. + +## Security + +- Use WPA2/WPA3 with a strong passphrase and change it for each deployment. The kit LAN is the security boundary. +- The database, Redis, RabbitMQ and RustFS require passwords but listen on the kit LAN so rick1 and rick2 can reach them. For stricter isolation, put responders on a separate guest WiFi network that may reach only rick1 port 443 and the DNS server. +- Store `.env`, `certs/private/` and backups encrypted when the kit is packed away. ## Troubleshooting -- `exec format error`: x86_64 binfmt is missing/disabled on the ARM64 web node. -- `WAIT_HOSTS` timeout: verify node addresses in `.env`, routing, and firewall rules. -- Browser login failure: accept/trust the API and events certificates, not only the web certificate. -- Nodes disagree about credentials: restore the same backed-up `.env` to all three; do not rerun setup separately. +| Symptom | Fix | +|---|---| +| `./rick.sh` says no role matches | The Pi's address isn't one of the `RICK_*_HOST` values in `.env`. Check `ip -4 addr` or set `RICK_ROLE`. | +| A health check fails | `./rick.sh status` and `./rick.sh logs ` on that node. Containers wait for the other nodes and restart until they are up. | +| Phones can't resolve `rgapi.rick.internal` | Router DNS records are missing, or the phone uses Private DNS, iCloud Private Relay, a VPN or mobile data. | +| Certificate error in a browser | Install the kit CA on that device; check the device's and the kit's clocks. | +| Android app shows a network error at login | Android apps don't trust the local CA. Use a public certificate (above). | +| App logs in but nothing updates | `rgevents.rick.internal` must resolve and be trusted too. | +| Web map is blank | Maps not imported, or rick4 not started with `--profile maps`. | +| `exec format error` | The Resgrid images aren't arm64; see [Raspberry Pi images](#before-you-deploy-raspberry-pi-images). | diff --git a/docusaurus.config.js b/docusaurus.config.js index 5afd6e6d..1f7f0cc3 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -139,10 +139,12 @@ const config = { position: 'left', items: [ {type: 'doc', docId: 'self-hosted/quick-start', label: 'Quick Start'}, - {type: 'doc', docId: 'self-hosted/installation', label: 'Installation'}, - {type: 'doc', docId: 'self-hosted/multi', label: 'Kubernetes & k3s'}, - {type: 'doc', docId: 'self-hosted/laptop', label: 'Windows Laptop / Desktop'}, + {type: 'doc', docId: 'self-hosted/installation', label: 'Installation (Single Server)'}, + {type: 'doc', docId: 'self-hosted/laptop', label: 'Windows Laptop (Offline)'}, {type: 'doc', docId: 'self-hosted/rick', label: 'Incident Command Kit (RICK)'}, + {type: 'doc', docId: 'self-hosted/multi', label: 'Kubernetes & k3s'}, + {type: 'doc', docId: 'self-hosted/certificates', label: 'Certificates & Hostnames'}, + {type: 'doc', docId: 'self-hosted/apps', label: 'Connecting the Mobile Apps'}, {type: 'doc', docId: 'reference/docker', label: 'Docker Reference'}, {type: 'doc', docId: 'reference/sizing', label: 'Sizing Guidelines'}, ], diff --git a/static/img/self-hosted/calls.png b/static/img/self-hosted/calls.png new file mode 100644 index 00000000..ea4452e8 Binary files /dev/null and b/static/img/self-hosted/calls.png differ diff --git a/static/img/self-hosted/dashboard.png b/static/img/self-hosted/dashboard.png new file mode 100644 index 00000000..44845f17 Binary files /dev/null and b/static/img/self-hosted/dashboard.png differ diff --git a/static/img/self-hosted/features-search.png b/static/img/self-hosted/features-search.png new file mode 100644 index 00000000..2ceb2c02 Binary files /dev/null and b/static/img/self-hosted/features-search.png differ diff --git a/static/img/self-hosted/k8s-calls.png b/static/img/self-hosted/k8s-calls.png new file mode 100644 index 00000000..4c2690d9 Binary files /dev/null and b/static/img/self-hosted/k8s-calls.png differ diff --git a/static/img/self-hosted/kubernetes.svg b/static/img/self-hosted/kubernetes.svg new file mode 100644 index 00000000..16a872c6 --- /dev/null +++ b/static/img/self-hosted/kubernetes.svg @@ -0,0 +1,42 @@ + + + + + + + Clients → DNS → load balancer / node IPs :443 + + + Kubernetes / k3s cluster · namespace resgrid · spread over several nodes + + Ingress (Traefik / nginx) + TLS secret resgrid-ingress-tls + + + + + + web × NDeployment + + api × NDeployment + + events × 1SignalR (Redis backplane) + + tts/tts/audio + + worker × 1init: create databases · migrations · jobs + + mcpcluster-internal only + + + + + db (PostgreSQL)StatefulSet + PVCor CloudNativePG / managed + + redisStatefulSet + PVC + + rabbitmqStatefulSet + PVCor Cluster Operator ×3 + + rustfs (S3)StatefulSet + PVC + + diff --git a/static/img/self-hosted/laptop-dashboard.png b/static/img/self-hosted/laptop-dashboard.png new file mode 100644 index 00000000..9cd2fd60 Binary files /dev/null and b/static/img/self-hosted/laptop-dashboard.png differ diff --git a/static/img/self-hosted/login.png b/static/img/self-hosted/login.png new file mode 100644 index 00000000..2dbb1e5c Binary files /dev/null and b/static/img/self-hosted/login.png differ diff --git a/static/img/self-hosted/map.png b/static/img/self-hosted/map.png new file mode 100644 index 00000000..b048ba8e Binary files /dev/null and b/static/img/self-hosted/map.png differ diff --git a/static/img/self-hosted/new-call.png b/static/img/self-hosted/new-call.png new file mode 100644 index 00000000..f3be4f5a Binary files /dev/null and b/static/img/self-hosted/new-call.png differ diff --git a/static/img/self-hosted/offline-map.png b/static/img/self-hosted/offline-map.png new file mode 100644 index 00000000..9d8a180f Binary files /dev/null and b/static/img/self-hosted/offline-map.png differ diff --git a/static/img/self-hosted/rick-calls-offline.png b/static/img/self-hosted/rick-calls-offline.png new file mode 100644 index 00000000..361bc076 Binary files /dev/null and b/static/img/self-hosted/rick-calls-offline.png differ diff --git a/static/img/self-hosted/rick-kit.svg b/static/img/self-hosted/rick-kit.svg new file mode 100644 index 00000000..24b47542 --- /dev/null +++ b/static/img/self-hosted/rick-kit.svg @@ -0,0 +1,77 @@ + + + + + + + + + + Responders' phones + Resgrid Responder + Unit apps + + Command laptop + Resgrid web app in a browser + + WiFi (WPA2/3) + WiFi or cable + + + + + + Hotspot router 192.168.50.1 + DHCP · DNS: rg / rgapi / rgevents → rick1 · no internet + + + + + Gigabit switch (PoE+ optional) + + + + + + + + + + rick1 · edge + 192.168.50.11 + Caddy :443 (HTTPS) + Web app + Events hub (SignalR) + Kit DNS (optional) + Time source (chrony) + + + rick2 · app + 192.168.50.12 + API :5152 + Worker (migrations, jobs) + Text-to-speech :5154 + MCP (local only) + Trackers / relay (opt.) + + + rick3 · data + 192.168.50.13 + PostgreSQL :5432 + resgrid, resgridoidc, + resgridworkers, resgriddoc + Backups (./rick.sh backup) + + + rick4 · infra + 192.168.50.14 + Redis :6379 + RabbitMQ :5672 + RustFS (S3) :9000 + Map tiles :5156 + + + + Clients only talk to rick1 on 443. rick1 forwards /api and /tts to rick2 and /tiles to rick4. + rick1 + rick2 use PostgreSQL on rick3 and Redis, RabbitMQ and RustFS on rick4. + Power-on: every container starts by itself and waits for the other nodes; boot order doesn't matter. + diff --git a/static/img/self-hosted/rick-login-offline.png b/static/img/self-hosted/rick-login-offline.png new file mode 100644 index 00000000..28938f5b Binary files /dev/null and b/static/img/self-hosted/rick-login-offline.png differ diff --git a/static/img/self-hosted/sign-up.png b/static/img/self-hosted/sign-up.png new file mode 100644 index 00000000..551e43db Binary files /dev/null and b/static/img/self-hosted/sign-up.png differ diff --git a/static/img/self-hosted/single-server.svg b/static/img/self-hosted/single-server.svg new file mode 100644 index 00000000..32146711 --- /dev/null +++ b/static/img/self-hosted/single-server.svg @@ -0,0 +1,50 @@ + + + + + + + Browsers · Responder and Unit apps + https://rg.… · https://rgapi.… · https://rgevents.… + + + + One Docker host (docker compose, project "resgrid") + + + Caddy :80 / :443 + HTTPS: Let's Encrypt, local CA or your certificate + + + + + + + webweb app · BFF + + apiREST API · login (OIDC) + + eventsreal-time (SignalR) + + tts · tiles/tts/audio · /tiles (opt.) + + + workerjobs · database migrations + + mcp :5155AI assistants (LAN only) + + tracker-gateway · relayoptional profiles + + + + + PostgreSQL 184 databasesvolume postgres-data + + Redis 8cache · SignalR backplane + + RabbitMQ 4.3message bus + + RustFS (S3)audio · imports + + One-shot jobs at start: db-init (databases) · rustfs-init (buckets) · caddy-init (local CA key) +