diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/404.html b/404.html new file mode 100644 index 00000000..29a7bc90 --- /dev/null +++ b/404.html @@ -0,0 +1,1649 @@ + + + +
+ + + + + + + + + + + + + + + + + + +This documents how to use Ansible playbooks to set up a production-like server installation. It differs from a production installation in that certificates must not be self-signed in a production environment.
+++These steps are for installing on a server OS directly and require experience with remote configuration and Linux administration.
+
You must have a local VM or remote server. See /packaging/vagrant/centos
for a Vagrant VM (CentOS 7) script for working example of creating a local VM.
Clone the main repository. The Ansible playbooks and templates are in that folder.
+git clone https://github.com/intrahealth/client-registry.git
+cd client-registry/packaging/ansible
+
Create a VM. Make sure to include a public ssh key for the user who will install prerequisites. Your SSH public key should be in .ssh/authorized_keys
on the remote host, ie:
cat ~/.ssh/id_rsa.pub | ssh user@remotehost 'cat >> .ssh/authorized_keys'
+
Hosts can be specified in inventory files or on the command line. To use Ansible with an inventory file, you must create a file or edit the one in the repository. There are yaml and ini formats supported.
+A hosts
file that has an entry for one server would be:
[servers]
+172.16.174.137
+
++Note that
+[servers]
is not necessary, it is way to tag groups of servers. The file may simply contain an IP address or domain.
To use the hosts file:
+ansible-playbook -i hosts someplaybook.yaml
+
Alternately, hosts may be specified on the command line (the comma is necessary even if there is only one host):
+ansible-playbook -i 172.16.168.158, someplaybook.yaml
+
A example playbook is provided to show how to create a opencr
user with sudo permissions using Ansible to be used with the host.
Create the opencr
user and gives it sudo access:
ansible-playbook -i hosts user.yaml
+
ansible-playbook -i hosts prep_centos.yaml -e user=opencr
+ansible-playbook -i hosts elasticsearch.yaml -e user=opencr
+ansible-playbook -i hosts tomcat.yaml -e user=opencr
+ansible-playbook -i hosts postgres.yaml -e user=opencr -e pgpass=hapi
+ansible-playbook -i hosts hapi.yaml -e user=opencr
+ansible-playbook -i hosts opencr.yaml -e user=opencr
+
An optional step but recommeded is to check the logs for services running after installation:
+ansible-playbook -i hosts troubleshoot.yaml -e user=opencr
+
OpenCR is now running. It will only allow requests from localhost (from the same server it is installed on).
+Visit: https://ipaddress:3000/crux
+HTTPS must be used.
+Warning
+If not running localhost, follow the next steps to create self-signed server and client certs, and copy them onto the server using an Ansible script below.
+Note
+These steps are automated in the certs.sh script, but please read through the steps to understand what is happening.
+To use:
+bash certs.sh <ip address or domain>
+Then run the ansible script to replace it on the server:
+ansible-playbook -i hosts servercerts.yaml -e user=opencr
Two certificate pairs are required, one pair for the server and one for the client generated from the server's. The existing self-signed server certs use localhost as the CN. This can be seen with the following for any cert:
+$ openssl x509 -in ../../server/certificates/server_cert.pem -text
+...
+ Subject: CN = localhost, O = Client Registry
+...
+
This means that new server and client certificates need to be generated with the IP address or domain for clients to access the client registry if it is not running on localhost.
+Caution
+Self-signed certificates must only be created for testing and demonstrations and in non-production settings.
+Make a note of your IP or domain for which you need to create a server cert. Run the following to create a new server cert/key pair. It will ask for a pass phrase (which is required in production) but -nodes option squashes that. This will create two files, server_key.pem and server_cert.pem. We can inspect the certificate to verify it has the IP address in the subject.
+# confirm ip being used
+cat hosts
+openssl req -x509 -newkey rsa:4096 -keyout server_key.pem -out server_cert.pem -days 365 -subj "/CN=172.16.168.172" -nodes
+# confirm new CN
+openssl x509 -in server_cert.pem -text
+
Now it is necessary to create new a new client cert based on the server cert. A key is first created, then the certificate, and they are packaged together in a p12 file.
+openssl req -newkey rsa:4096 -keyout ansible_key.pem -out ansible_csr.pem -nodes -subj "/CN=ansible"
+openssl x509 -req -in ansible_csr.pem -CA server_cert.pem -CAkey server_key.pem -out ansible_cert.pem -set_serial 01 -days 36500
+# requires specifying an export key
+openssl pkcs12 -export -in ansible_cert.pem -inkey ansible_key.pem -out ansible.p12
+
The client certs can be placed in the existing folder for client certs for convenience.
+# add client certs
+cp ansible_key.pem ../../server/sampleclientcertificates/
+cp ansible_csr.pem ../../server/sampleclientcertificates/
+cp ansible_cert.pem ../../server/sampleclientcertificates/
+cp ansible.p12 ../../server/sampleclientcertificates/
+
++Server certs may also be copied into ../../certificates/ for convenience but this will overwrite the copies and break localhost if the repo is used to upload code to GitHub.
+
Warning
+To complete the process, server certs need to be placed into the server. This will be done using an Ansible script below.
+Copy the server certs to the server and restart opencr service to use them.
+ansible-playbook -i hosts servercerts.yaml -e user=opencr
+
Test (for servers not localhost):
+# this assumes the server cert and client cert are in this (/packaging/ansible) directory
+# replace the path to your copy of the repo
+# replace the ip address of the server
+curl --cert ansible.p12 --cert-type p12 --cacert server_cert.pem -d @/Users/richard/src/github.com/intrahealth/client-registry/DemoData/patient1_openmrs.json -H "Content-Type: application/json" -XPOST https://172.16.168.172:3000/Patient
+
As necessary, add additional ssh keys to the user opencr
. (Ensure that the user's public key is available on github, ie. https://github.com/citizenrich.keys):
ansible-playbook -i hosts keys.yaml
+
OpenCR is not one application, instead it's a set of applications that work together in the Open Health Information Exchange (OpenHIE) architecture to serve point-of-service systems, like EMRs, insurance mechanisms, and labs.
+Note
+This is not an OpenHIE product. The OpenHIE community of practice does not produce software products. Rather OpenHIE produces an architecture specification and is composed of a large, global community of practice around standards-based health information exchanges, particularly in low resource settings. Please join us!
+The OpenCR architecture includes:
+The primary datastore is the database of HAPI FHIR Server. This means that while an ES cluster should be backed-up, the ES index can be rebuilt from HAPI.
+Either Postgres or MySQL are recommended to be used with HAPI FHIR Server. In production, database should be cloned or replicas created and cloned and those backups tested.
+Depending on the database, there are separate processes to backup data itself in a database and information about users, groups and other metadata.
+It is recommended to create a backup and recovery policy (data and metadata, timeframes, full-versus-incremental, from replicas or not).
+Backups should be tested in a non-production system for their ability to be used for recovery. There are existing online resources on how to test backups.
+A backup policy should include scheduled recovery tests to ensure that backups are suitable.
+Often there are many records of the same person but in many people in different systems. The purpose of the Client Registry is to link patients in different systems, but not to transfer any data, neither clinical records nor demographic data.
+Caution
+The Client Registry does not store clinical information. Having the Client Registry enables the ability to create a Shared Health Record in the future.
+Note
+OpenSearch and ElasticSearch uses exactly the same technology and have almost the same configuration, i.e ports etc. The configuration file of OpenCR uses parameter elasticsearch to refer to either ElasticSearch or OpenSearch.
+The Client Registry stores the patient demographic data submitted to it in queries. The Client Registry stores demographic data at least in the HAPI FHIR Server, which can have any database backend an implementer chooses to use.
+OpenSearch/ElasticSearch (ES) is a required search engine for matching, and may requires configuration.
+JSON files are used to configure the system. Later iterations will support environment variables and a graphical interface. See https://github.com/openhie/client-registry/tree/master/server/config for example configuration files discussed here.
+A central application is the Client Registry Service, as distinct from the larger Client Registry platform. There are two options for running the application, as an OpenHIM mediator or as standalone application.
+Choose running the app standalone when:
+Choose running the app as a mediator when:
+Many configuration options relate to privacy and security. These steps are critical to address. See the security page
+Whether in standalone or as a mediator, the Client Registry must interact only with known, trusted clients with TLS certificates. Clients must be registered and certificates assigned to them.
+In standalone mode, the server runs TLS by default, and requires signed certificates. Client certificate needs can be turned off in OpenHIM when running as a mediator and this feature must be regularly audited to ensure security.
+The default ports are as follows:
+In server/config/config_development_template.json
there is a template for configuration.
Contents of server/config/config_development_template.json
{
+ "app": {
+ "port": 3000,
+ "installed": false
+ },
+ "mediator": {
+ "api": {
+ "username": "root@openhim.org",
+ "password": "openhim-password",
+ "apiURL": "https://localhost:8080",
+ "trustSelfSigned": true,
+ "urn": ""
+ },
+ "register": false
+ },
+ "fhirServer": {
+ "baseURL": "http://localhost:8080/clientregistry/fhir",
+ "username": "hapi",
+ "password": "hapi"
+ },
+ "elastic": {
+ "server": "http://localhost:9200",
+ "username": "",
+ "password": "",
+ "max_compilations_rate": "10000/1m",
+ "index": "patients"
+ },
+ "structureDefinition": {
+ "reportRelationship": "patientreport"
+ },
+ "matching": {
+ "tool": "mediator"
+ },
+ "systems": {
+ "openmrs": {
+ "uri": "http://clientregistry.org/openmrs"
+ },
+ "dhis2": {
+ "uri": "http://clientregistry.org/dhis2"
+ },
+ "lims": {
+ "uri": "http://clientregistry.org/lims"
+ },
+ "brokenMatch": {
+ "uri": "http://ihris.org/CR/brokenMatch"
+ }
+ },
+ "sync": {
+ "lastFHIR2ESSync": "1970-01-01T00:00:06"
+ },
+ "__comments": {
+ "matching.tool": "this tells if the app should use mediator algorithms or elasticsearch algorithms for matching, two options mediator and elasticsearch"
+ }
+}
+
app.port
is the port the application will run on.
app.installed
can be left to True. This tells the Client Registry Service to load structure definitions into HAPI FHIR Server, otherwise it will not.
mediator.register
to true if the application will run as a mediator. Or, to false if the app will run as standalone.
mediator.api.xx
settings are only if running as a mediator.
mediator.api.username | password
must be different. The existing settings are defaults and must be changed when configuring the OpenHIM.
mediator.api.trustSelfSigned
should be set to false in production or any sensitive environment. True is only for demonstrations.
The currently supported FHIR version is R4.
+fhirServer.baseURL
is the default. Note that it may change depending on the way HAPI is installed. It may, for example, default to a baseURL of http://localhost:8080/baseR4/.
fhirServer.username | password
must be changed from defaults in HAPI.
In the scenario where only a few fields require changing it could be useful to configure these fields with Environment variables. +Any value within the openCR config file object can be manually configured via enviroment variable.
+++OpenHIM Mediator config cannot be configured in this manner
+
OpenCR uses the nconf
library to store app config. nconf stores the config files in memory and environemnt variables take precedence.
+See the config object below:
{
+ "app": {
+ "port": 3000
+ }
+ ,
+ ...
+}
+
To configure the port field tfrom 300 to 3003 the syntax would be: app:port=3003
.
+However, the colon syntax does not work well in many instances. To handle this we have configured nconf to accept __ as a replacement for the colon separator. What this means is that to configure the port field you would use the following, app__port=3003
.
+This syntax works for deeper nested config as well. For example, let's configure password in the config object below:
{
+ ...
+ "mediator": {
+ "api": {
+ "password": "openhim-password"
+ }
+ }
+}
+
The syntax to change the field from openhim-password to newPassword would be mediator__api__password=newPassword
For OpenSearch/ElasticSearch, the relationship between patient resources in FHIR and what fields are synchronized in OpenSearch/ElasticSearch must be explicitly defined. This is termed the Report Relationship mapping. One must define what resource to be used (patient) and what fields need to be available in OpenSearch/ElasticSearch. After this, the Client Registry reads these fields, and populates OpenSearch/ElasticSearch with the information.
+In resources/Relationships/PatientRelationship.json
there is a template for configuration.
Contents of resources/Relationships/PatientRelationship.json
{
+ "resourceType": "Basic",
+ "id": "patientreport",
+ "meta": {
+ "versionId": "1",
+ "lastUpdated": "2019-07-30T07:34:24.098+00:00",
+ "profile": ["http://ihris.org/fhir/StructureDefinition/iHRISRelationship"]
+ },
+ "extension": [
+ {
+ "url": "http://ihris.org/fhir/StructureDefinition/iHRISReportDetails",
+ "extension": [
+ {
+ "url": "label",
+ "valueString": "Patient Report"
+ },
+ {
+ "url": "name",
+ "valueString": "patients"
+ },
+ {
+ "url": "http://ihris.org/fhir/StructureDefinition/iHRISReportElement",
+ "extension": [
+ {
+ "url": "label",
+ "valueString": "gender"
+ },
+ {
+ "url": "name",
+ "valueString": "gender"
+ }
+ ]
+ },
+ {
+ "url": "http://ihris.org/fhir/StructureDefinition/iHRISReportElement",
+ "extension": [
+ {
+ "url": "label",
+ "valueString": "birthDate"
+ },
+ {
+ "url": "name",
+ "valueString": "birthDate"
+ }
+ ]
+ },
+ {
+ "url": "http://ihris.org/fhir/StructureDefinition/iHRISReportElement",
+ "extension": [
+ {
+ "url": "label",
+ "valueString": "given"
+ },
+ {
+ "url": "name",
+ "valueString": "name.where(use='official').last().given"
+ }
+ ]
+ },
+ {
+ "url": "http://ihris.org/fhir/StructureDefinition/iHRISReportElement",
+ "extension": [
+ {
+ "url": "label",
+ "valueString": "family"
+ },
+ {
+ "url": "name",
+ "valueString": "name.where(use='official').last().family"
+ }
+ ]
+ },
+ {
+ "url": "http://ihris.org/fhir/StructureDefinition/iHRISReportElement",
+ "extension": [
+ {
+ "url": "label",
+ "valueString": "fullname"
+ },
+ {
+ "url": "name",
+ "valueString": "name.where(use='official').last().text"
+ }
+ ]
+ },
+ {
+ "url": "http://ihris.org/fhir/StructureDefinition/iHRISReportElement",
+ "extension": [
+ {
+ "url": "label",
+ "valueString": "phone"
+ },
+ {
+ "url": "name",
+ "valueString": "telecom.where(system='phone').value"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "code": {
+ "coding": [
+ {
+ "system": "http://ihris.org/fhir/ValueSet/ihris-resource",
+ "code": "iHRISRelationship"
+ }
+ ],
+ "text": "iHRISRelationship"
+ },
+ "subject": {
+ "reference": "StructureDefinition/Patient"
+ }
+}
+
If using OpenHIM, it must be configured for proper clients and roles to accept and forward requests from the Client Registry. An example export of a working JSON configuration that can be imported for development purposes is available.
+ +Contents of server/config/mediator.json
{
+ "urn": "urn:uuid:4bc42b2f-b5a8-473d-8207-5dd5c61f0c4a",
+ "version": "0.0.1",
+ "name": "Client Registry",
+ "description": "Uganda Client Registry",
+ "config": {
+ "fhirServer": {
+ "username": "hapi",
+ "password": "hapi",
+ "baseURL": "http://localhost:8080/hapi/fhir"
+ },
+ "elastic": {
+ "server": "http://localhost:9200",
+ "username": "",
+ "password": "",
+ "max_compilations_rate": "10000/1m",
+ "index": "patients"
+ },
+ "matching": {
+ "tool": "elasticsearch"
+ }
+ },
+ "configDefs": [
+ {
+ "param": "fhirServer",
+ "displayName": "FHIR Server",
+ "description": "FHIR Server Configuration Details",
+ "type": "struct",
+ "template": [
+ {
+ "type": "string",
+ "description": "The base URL (e.g. http://localhost:8080/hapi/fhir)",
+ "displayName": "Base URL",
+ "param": "baseURL"
+ },
+ {
+ "type": "string",
+ "description": "Username required to access FHIR server",
+ "displayName": "Username",
+ "param": "username"
+ },
+ {
+ "type": "password",
+ "description": "Password required to access FHIR server",
+ "displayName": "Password",
+ "param": "password"
+ }
+ ],
+ "values": []
+ },
+ {
+ "param": "elastic",
+ "displayName": "Elasticsearch Server",
+ "description": "Elasticsearch Server Configuration Details",
+ "type": "struct",
+ "template": [
+ {
+ "type": "string",
+ "description": "The base URL (e.g. http://localhost:9200)",
+ "displayName": "Base URL",
+ "param": "server"
+ },
+ {
+ "type": "string",
+ "description": "Username required to access elasticsearch server",
+ "displayName": "Username",
+ "param": "username"
+ },
+ {
+ "type": "password",
+ "description": "Password required to access elasticsearch server",
+ "displayName": "Password",
+ "param": "password"
+ },
+ {
+ "type": "string",
+ "description": "Number of requests to compile per minute",
+ "displayName": "Maximum Compilations Rate",
+ "param": "max_compilations_rate"
+ },
+ {
+ "type": "string",
+ "description": "index to use for data storage",
+ "displayName": "Index Name",
+ "param": "index"
+ }
+ ],
+ "values": []
+ },
+ {
+ "param": "matching",
+ "displayName": "FHIR Server",
+ "description": "FHIR Server Configuration Details",
+ "type": "struct",
+ "template": [
+ {
+ "type": "option",
+ "values": ["mediator", "elasticsearch"],
+ "description": "Tool to Use for Matching",
+ "displayName": "Tool to Use for Matching",
+ "param": "tool"
+ }
+ ],
+ "values": []
+ }
+ ],
+ "defaultChannelConfig": [
+ {
+ "requestBody": true,
+ "responseBody": true,
+ "name": "Add Patients",
+ "description": "Post a new patient into the client registry",
+ "urlPattern": "/addPatient",
+ "matchContentRegex": null,
+ "matchContentXpath": null,
+ "matchContentValue": null,
+ "matchContentJson": null,
+ "pollingSchedule": null,
+ "tcpHost": null,
+ "tcpPort": null,
+ "autoRetryPeriodMinutes": 60,
+ "autoRetryEnabled": false,
+ "rewriteUrlsConfig": [],
+ "addAutoRewriteRules": true,
+ "rewriteUrls": false,
+ "status": "enabled",
+ "alerts": [],
+ "txRerunAcl": [],
+ "txViewFullAcl": [],
+ "txViewAcl": [],
+ "properties": [],
+ "matchContentTypes": [],
+ "routes": [
+ {
+ "name": "Add Patient",
+ "secured": false,
+ "host": "localhost",
+ "port": 3000,
+ "path": "/addPatient",
+ "pathTransform": "",
+ "primary": true,
+ "username": "",
+ "password": "",
+ "forwardAuthHeader": false,
+ "status": "enabled",
+ "type": "http"
+ }
+ ],
+ "authType": "public",
+ "whitelist": [],
+ "allow": [],
+ "type": "http",
+ "methods": ["POST"]
+ }
+ ],
+ "endpoints": [
+ {
+ "name": "Activate Client Registry",
+ "host": "localhost",
+ "path": "/addPatient",
+ "port": 3000,
+ "primary": true,
+ "forwardAuthHeader": false,
+ "status": "enabled",
+ "type": "http"
+ }
+ ],
+ "_uptime": 2201.945,
+ "_lastHeartbeat": "2017-12-15T03:47:03.365Z",
+ "_configModifiedTS": "2017-12-15T02:52:49.054Z"
+}
+
Demographic data from submitting systems is stored in HAPI FHIR. It is also recommended that the demographic data that is primarily stored in HAPI FHIR be indexed into Elasticsearch.
+For match processing, there are two options. One is run in mediator-only mode, which is highly flexible and supports a handful of algorithms that can be chained together. Additional algorithms can be added as needed.
+The second is to use ES. ES is very fast and supports compound queries but currently only supports Levenshtein distance. When using ES, every request to the FHIR Server is cached in ES.
+(One additional caveat for Levenshtein distance is that the mediator-only matching can support edit distances exceeding two, while ES edit distance cannot exceed two.)
+Every client wishing to use the Client Registry must be authenticated and authorized. See the configuration page for more information.
+Decision rules determine how matches are made among records, for example, by using a certain algorithm on one field and a different algorithm on another.
+Let's use the below example:
+rules.givenName
is used as one rule on the field givenName.
rules.givenName.algorithm
defines an algorithm, in this instance Jaro-Winkler, and an threshold for that algorithm unique to it.
rules.givenName.path
is a required FHIRpath for the fields, a standard way to define how to traverse a FHIR resource. In future, a GUI may be used for defining the FHIRpath.
By default, all of the rules are chained together in a logical AND statement. In ES the search queries are assembled into compound queries.
+ +Contents of server/config/decision_rules.json
{
+ "__comments": {
+ "path": "Its a fhir path, for syntax refer to https://www.hl7.org/fhir/fhirpath.html",
+ "type": "String, Date, Number or Boolean",
+ "threshold": {
+ "levenshtein": "Lower the number, the closer the match, 0 being exact match",
+ "jaro-winkler": "number between 0 and 1, where 0 for no match and 1 for exact match"
+ }
+ },
+ "rules": {
+ "givenName": {
+ "algorithm": "jaro-winkler",
+ "threshold": 0.89,
+ "path": "name.where(use='official').last().given",
+ "type": "string",
+ "systems": ["system1", "system2", "system3"]
+ },
+ "familyName": {
+ "algorithm": "damerau-levenshtein",
+ "threshold": 3,
+ "path": "name.where(use='official').last().family",
+ "type": "String"
+ },
+ "gender": {
+ "algorithm": "exact",
+ "path": "gender",
+ "type": "String"
+ }
+ }
+}
+
Time to complete
+10 Minutes
+Warning
+This guide is for demonstrations or tests only, not for servers or production environments.
+The easiest way to get started with OpenCR is to use Docker to launch ElasticSearch and HAPI FHIR Server and run the OpenCR Service directly. By running the OpenCR Service directly, it is easy to revise and reload decision rules.
+These instructions have been tested on Linux and macOS.
+Note
+This installation method requires some familiarity with the command line.
+https://github.com/intrahealth/client-registry.git
+cd client-registry
+
docker --version
+
Start ElasticSearch and HAPI FHIR Server using Docker.
+Warning
+You cannot use the existing hosted ElasticSearch image because OpenCR requires two plugins to be installed. The docker-compose file provided uses the Dockerfile-es which builds an ES image with the plugins.
+docker-compose up fhir es
+
cd server
+npm install
+
cp config/config_docker_template.json config/config_docker.json
+
# from client-registry/server
+sudo NODE_ENV=docker node lib/app.js
+
+++
sudo
is needed as OpenCR requires access to /var/log for logging. This requirement may be changed in the future.
docker-compose -f docker-compose.cicd.yml up -d
+
++The flag
+-d
runs the processes in the background.
If you want to add dockerised OpenCR to a system with different docker dependency names, you can add new config files with the following script changes.
+In this scenario, we are going to change the HAPI-FHIR container name to test-fhir
.
To start, open the /server/config/config_cicd_template.json
file in a text editor and in the "fhirServer":
config section make the following change:
...
+"fhirServer": {
+ "baseURL": "http://test-fhir:8080/fhir",
+ "username": "hapi",
+ "password": "hapi"
+},
+...
+
With that config in place we need to volume in this new config file into our docker-compose.cicd.yml
file. Open this file in a text editor. We need three changes, first change the depends_on value from fhir to test-fhir
. Then, add a new environment variable HAPI_FHIR_URL
with the value http://test-fhir:8080/fhir/metadata. This url will be used to check the HAPI FHIR instance is running. Finally, add the volumes config to opencr. Your opencr
config section should resemble this:
opencr:
+ container_name: opencr
+ image: intrahealth/opencr
+ ports:
+ - "3000:3000"
+ depends_on:
+ - test-fhir
+ - es
+ restart: always
+ environment:
+ - NODE_ENV=cicd
+ - HAPI_FHIR_URL=http://test-fhir:8080/fhir/metadata
+ volumes:
+ - ./server/config/config_cicd_template.json:/src/server/config/config_cicd.json
+
Now we can start the system with the following:
+docker-compose -f docker-compose.cicd.yml up -d
+
++The flag
+-d
runs the processes in the background.
To see the container statuses run the following command:
+docker ps -a
+
++The flag
+-a
will include containers that are not running (i.e. from errors or a manual container stop)
To see the logs of a component run the following command:
+docker logs -f <component>
+
++Components are:
+opencr
,es
, andfhir
To remove OpenCR and its dependencies, run the following:
+docker-compose -f docker-compose.cicd.yml down
+
Time to complete
+60 Minutes
+Warning
+This guide is for demonstrations or tests only, not for production environments.
+Note
+This installation method requires familiarity with the command line.
+For non-production environments, the HAPI maintainers provide a simple CLI-based tool to run it.
+The only required dependency is Java >= 8 (1.8).
+See HAPI FHIR CLI for instructions for the OS of choice.
+The Client Registry requires FHIR version R4 and HAPI must be started for this version. To run HAPI:
+hapi-fhir-cli run-server -v r4
+
The HAPI Web Testing UI is available at http://localhost:8080/ The Web Testing UI should be disabled for production. It allows the viewing of any resource on the server.
+The FHIR Base URL is at http://localhost:8080/baseR4/
+Visit http://localhost:8080/ to ensure HAPI is up and running or
+curl -X GET "localhost:8080/baseR4/Patient?"
+
openCR supports both elasticsearch and opensearch, you are free to use either of them but we do recommend opensearch because of elasticsearch license restrictions. Follow instructions below to either install opensearch or elasticsearch
+Install and start opensearch for the intended OS. See the install instructions here
+The required version is >=2.1.
+The phonetic analysis package must be installed. For example:
+/usr/share/opensearch/bin/opensearch-plugin install analysis-phonetic
+
The string similarity plugin must be installed. See: https://github.com/DigitalSQR/record-linkage/releases
+Once installed and started, ensure that opensearch is up and running:
+curl -X GET "localhost:9200/_cat/health?v&pretty"
+
Status should be yellow for a single-node cluster.
+Install and start ES for the intended OS. See the ES install instructions
+The required version is >=7.6.
+The phonetic analysis package must be installed. For example:
+/usr/share/elasticsearch/bin/elasticsearch-plugin install analysis-phonetic
+
The string similarity plugin must be installed. See: https://github.com/intrahealth/similarity-scoring
+Once installed and started, ensure that ES is up and running:
+curl -X GET "localhost:9200/_cat/health?v&pretty"
+
Status should be yellow for a single-node cluster.
+Clone the repository into a directory of choice.
+git clone https://github.com/intrahealth/client-registry.git
+
Enter the server directory, install node packages.
+cd client-registry/server
+npm install
+
Copy and edit the configuration file to your liking.
+cp config/config_development_template.json config/config_development.json
+# edit the servers...
+
The minimum changes to start a running standalone system are:
+fhirServer.baseURL
to "http://localhost:8080/baseR4/"Run the server from inside client-registry/server:
+# from client-registry/server
+sudo NODE_ENV=development node lib/app.js
+
OpenCR may require access to /var/log for logging. This requirement may be changed in the future.
+Congratulations! Now it's time to run a query.
+ + + + + + + + + + + + + + + + + + + + +Caution
+Installing and maintaining a production installation is not trivial. This installation method requires strong familiarity with the command line and expertise administering Linux environments.
+The core production stack consists of four components:
+Either
+OR
+Optional components:
+Linux is the expected operating system for production.
+It is critical that systems administrators note the version compatibilities outlined below. This guide does not cover most aspects of enterprise systems administration, rather it attempts to cover the OpenCR platform. If there are key areas missing, please open an issue on GitHub.
+HAPI FHIR must use a database backend in production. HAPI FHIR stores the patient demographic data from queries. If the data is lost, then OpenCR data is unrecoverable.
+Caution
+In production, Postgres should run on multiple nodes with replication. This is to ensure high availability and backups of the data.
+/usr/share/elasticsearch/bin/elasticsearch-plugin install analysis-phonetic
+
/usr/share/elasticsearch/bin/elasticsearch-plugin install analysis-phonetic
+
Caution
+ES is not production-ready when run as one single node. It is recommended to run ES on several nodes. Those nodes can also run followers of Postgres.
+Clone the repository into a directory of choice.
+git clone https://github.com/intrahealth/client-registry.git
+
Enter the server directory, install node packages.
+cd client-registry/server
+npm install
+
Copy and edit the configuration file to your liking.
+cp config/config_development_template.json config/config_development.json
+# edit the servers...
+
The minimum changes to start a running standalone system are:
+fhirServer.baseURL
to "http://localhost:8080/baseR4/"Run the server from inside client-registry/server:
+node lib/app.js
+
OpenHIM supports the last 2 versions of NodeJS LTS and requires MongoDB.
+ES is a web service around the Apache Software Foundation-supported Lucene search engine. ES provides a JSON-based REST API, cluster management, and other value-add on top of Lucene. Many of the features discussed below are actually in Lucene, and are not specific to ES, although these docs refer to ES as including Lucene features. This can be confusing. ES means ES which includes Lucene.
+Data fields to be indexed in ES require that they first be mapped. The CR mediator takes a mapping config file and generates the mapping based on it. Then, records are submitted to and indexed by ES. Indexes are created separated into segments. Segments can be on one system or across server nodes in cluster. When searches happen, the segments are searched in parallel, and then the results merged.
+For more information, see: Elasticsearch from the bottom up (EuroPython 2014 - Start at 18:28 unless you want to get deep into Lucene.): https://www.youtube.com/watch?v=PpX7J-G2PEo
+An important distinction is between filters and queries in ES. For CR purposes, filters can be used for blocking. Queries result in a score that is assigned to the result based on how well it matches the query. For more, eee: https://logz.io/blog/elasticsearch-queries/ Note that filters are cached, and thus faster. Also, query clauses can be used as either a filter or a query. Since filters are boolean (true/false), they are not scored.
+There are queries of diverse types, including Boolean, compound (chaining queries together), fuzzy matching, and other types. Queries result in a score that is assigned to the result based on how well it matches the query. With regard to risk for the Uganda use case, we have included blocking (filters) to meet the use requirements and the required query types.
+For more, see ElasticSearch Queries +And the ES Query Domain Specific Language (DSL)
+ + + + + + + + + + + + + + + + + + + + +Demonstration data is provided in the /tests directory in the code repository.
+cd tests
+npm install
+
The /tests/uploadCSV.js script is used to upload the CSV data in the /tests directory.
+If using OpenHIM, then change the auth options and the IP address/hostname in /tests/uploadCSV
+# make a copy to modify
+cp uploadCSV.js uploadCSV_mychanges.js
+
The defaults are:
+const options = {
+url: 'http://localhost:5001/Patient',
+auth,
+json: entry.resource,
+};
+
Edit uploadCSV_mychanges.js. If not running OpenHIM then change:
+* remove auth
and change it to agentOptions
+* Change the IP address/hostname as required, for example for Docker: 'https://localhost:3000/Patient'.
After the edits, the code block looks like this:
+const options = {
+url: 'https://localhost:3000/Patient',
+agentOptions,
+json: entry.resource,
+};
+
Notice the https as without OpenHIM the OpenCR Service encrypts the connections using TLS instead of OpenHIM doing so.
+While in the /tests directory, ensure that OpenCR is running and run the script, with the required argument of the CSV:
+sudo node uploadCSV_mychanges.js uganda_data_v21_20201501.csv
+
Caution
+The script may take several hours to process all of the records.
+Select an installation method based on your requirements.
+Hint
+If you've used Docker before, it's the fastest way to evaluate OpenCR.
+Local installation (Docker):
+Local installation (Direct):
+Server installation:
+OpenCR is not one application, it's several and is expected to be run on Linux. Persons installing and managing OpenCR require advanced expertise with Linux. Here are the major topics where knowledge is required:
+sudo
access.For installing and managing an existing OpenCR installation, there are a handful of commands that can be learned.
+Java: HAPI FHIR Server and OpenSearch/ElasticSearch are written in Java. Java applications are generally built with frameworks for common design patterns and often built using Gradle or Maven.
+JavaScript and Node: The OpenCR Service is written in Node, a popular JavaScript framework for building RESTful applications. Node applications are packaged around the Node Package Manager.
+Postgres/MySQL: Either Postgres or MySQL are recommended to be used with HAPI FHIR Server. Administrators should become familiar with backup and recovery procedures.
+A system querying the Client Registry needs a server-issued certificate or it will not be authorized to use the service.
+The way that this works is that a server creates a certificate for a client. The certificate is signed by the server issuing it. The querying system then uses that certificate that has been issued to them in their requests. The server's public key is used by the querying system to verify that the certificate being sent is how the server verifies that the certificate was created by them.
+There is a set of generated certificates for testing and demonstrations. They are not appropriate for production.
+From inside the /client-registry/server
directory, send a cURL query using the provided example JSON file:
curl --cert sampleclientcertificates/openmrs.p12 --cert-type p12 --cacert certificates/server_cert.pem -d @../DemoData/patient1_openmrs.json -H "Content-Type: application/json" -XPOST https://localhost:3000/fhir/Patient
+
Should result in a successful result in stdout:
+info: Received a request to add new patient {"timestamp":"2020-01-28 14:29:20"}
+info: Searching to check if the patient exists {"timestamp":"2020-01-28 14:29:20"}
+info: Getting http://localhost:8080/baseR4/Patient?identifier=431287 from server {"timestamp":"2020-01-28 14:29:20"}
+info: Patient [{"system":"http://clientregistry.org/openmrs","value":"431287"},{"system":"http://system1.org","value":"12349","period":{"start":"2001-05-06"},"assigner":{"display":"test Org"}}] doesnt exist, adding to the database {"timestamp":"2020-01-28 14:29:20"}
+
Benchmarking will be completed in future phases to make recommendations for medium to heavy workloads. The below resource suggestions should be revised based on benchmarking for the particular context into which OpenCR is being deployed.
+For an MVP in a production environment where potential data loss is acceptable, a single large server can be used. ES has high memory requirements.
+Memory usage depends on the number of records and the performance required. At minimum: 32GB with 24GB free for OpenCR is recommended for light loads if using one VM.
+This depends heavily on the workload. Expect 200GB at minimum per node.
+ + + + + + + + + + + + + + + + + + + + +Warning
+It is difficult (and irresponsible) to try to explain all of the best practices in computer security. This page focuses on how security is addressed in the Open Client Registry. The information may be incomplete. Where there is more clarity or information needed, please provide feedback in an issue on GitHub so that it can be added.
+This page addresses several security areas, including hardening, user authentication, node authentication, auditing, and non-production (demos, tests) configurations.
+For links to information on server resource planning see the requirements page.
+Warning
+Server and network hardening and production best practices are out of scope. This document only attempts to capture aspects relevant to the Client Registry.
+Hardening and production best practices include:
+See, for example, the Guide to General Server Security: Recommendations of the National Institute of Standards and Technology by Karen Scarfone, Wayne Jansen, Miles Tracy, July 2008, (NIST Special Publication 800-123).
+In addition to the above general hardening practices that should be followed, some additional areas are important for the Client Registry.
+OpenHIM supports the Audit Trail and Node Authentication (ATNA) Integration Profile, which establishes a standard for responsibly storing audit events. It is highly recommended that the OpenHIM be configured as such but only after it is ensured that all default passwords have been changed and the OpenHIM is operating on a local subnet and thus not exposed externally. See the above hardening notes.
+See the OpenHIM user guide for information on ATNA configuration.
+In non-production settings only may self-signed certificates be created for testing and demonstrations. An example is as follows:
+openssl req -newkey rsa:4096 -keyout dhis2_key.pem -out dhis2_csr.pem -nodes -days 365 -subj "/CN=dhis2"
+openssl x509 -req -in dhis2_csr.pem -CA ../certificates/server_cert.pem -CAkey ../certificates/server_key.pem -out dhis2_cert.pem -set_serial 01 -days 36500
+openssl pkcs12 -export -in dhis2_cert.pem -inkey dhis2_key.pem -out dhis2.p12
+