diff --git a/.env b/.env index cb1a91b2..8d6e8f7f 100644 --- a/.env +++ b/.env @@ -15,3 +15,6 @@ OPENHIM_PORT=9201 ISANTEPLUS_DB_PORT=3307 OPENMRS_DB_PORT=3308 + +ADMIN_PW=openhim-pw +POSTMAN_COLLECTION= \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..872c334c --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,89 @@ +name: CI +on: + push: + branches: + - main + pull_request: + branches: + - main + release: + types: [published] + workflow_dispatch: +jobs: + CI: + timeout-minutes: 10 + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v1 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Pull containers + run: docker-compose -f docker-compose.ports.yml pull + + - name: Cache containers + uses: satackey/action-docker-layer-caching@v0.0.11 + continue-on-error: true + + - name: generate certs + run: docker-compose -f docker-compose.ports.yml up -d certgen + + - name: Start core containers + run: docker-compose -f docker-compose.ports.yml up -d nginx openhim-core openhim-console mongo-db openhim-config + + - name: Sleep for 10 seconds + run: sleep 30 + shell: bash + + - name: Display docker logs for openhim config + run: docker-compose -f docker-compose.ports.yml logs openhim-config + + - name: Start up support containers + run: docker-compose -f docker-compose.ports.yml up -d shr-fhir opencr-fhir opencr-es kafka zookeeper + + - name: Sleep for 90 seconds + run: sleep 90 + shell: bash + + - name: Display docker logs for SHR & OpenCR + run: docker-compose -f docker-compose.ports.yml logs shr-fhir opencr-fhir + + - name: Start up mediators + run: docker-compose -f docker-compose.ports.yml up -d shr opencr + + - name: Sleep for 30 seconds + run: sleep 30 + shell: bash + + - name: Display docker logs for SHR & OpenCR + run: docker-compose -f docker-compose.ports.yml logs shr opencr + + - name: Display container status + run: docker-compose -f docker-compose.ports.yml ps + + - name: Run General Tests (https://www.postman.com/workspace/Haiti-SEDISH~4ada6d5c-42b6-483b-84e6-e2b5e08e1123/collection/1525496-6f854cdb-67cd-447e-950a-25cdf2d85186) + env: + POSTMAN_COLLECTION: https://www.getpostman.com/collections/46fd37386092a9f460e4 + run: docker-compose --profile test -f docker-compose.ports.yml up --exit-code-from newman newman + + - name: Run CR Tests (https://www.postman.com/workspace/Haiti-SEDISH~4ada6d5c-42b6-483b-84e6-e2b5e08e1123/collection/1525496-943a668e-664f-44a3-86b5-a4d4bc14c0e9) + env: + POSTMAN_COLLECTION: https://www.getpostman.com/collections/4d682cbb222bb538d365 + run: docker-compose --profile test -f docker-compose.ports.yml up --exit-code-from newman newman + + # - name: Run Laboratory Tests (https://www.postman.com/workspace/Haiti-SEDISH~4ada6d5c-42b6-483b-84e6-e2b5e08e1123/collection/1525496-f269b96a-22e3-4a1f-8333-04d2cd01c1aa) + # env: + # POSTMAN_COLLECTION: https://www.getpostman.com/collections/4f2328a2ce056ff876e4 + # run: docker-compose --profile test -f docker-compose.ports.yml up --exit-code-from newman newman + + # - name: Run Continuum of Care Tests (https://www.postman.com/workspace/Haiti-SEDISH~4ada6d5c-42b6-483b-84e6-e2b5e08e1123/collection/1525496-6514deeb-c038-49fd-b510-c55b3dc20bad) + # env: + # POSTMAN_COLLECTION: https://www.getpostman.com/collections/0d397620f00804b00d75 + # run: docker-compose --profile test -f docker-compose.ports.yml up --exit-code-from newman newman + + - name: Stop containers + if: always() + run: docker-compose -f docker-compose.ports.yml down -v diff --git a/.gitignore b/.gitignore index c86697be..74e6e2af 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,4 @@ server/log hapi.properties configs/shr/.env configs/opencr-hapi/.env + diff --git a/.postman/postman_env.ci.json b/.postman/postman_env.ci.json new file mode 100644 index 00000000..f782cb08 --- /dev/null +++ b/.postman/postman_env.ci.json @@ -0,0 +1,29 @@ +{ + "id": "2432b862-e96d-4c54-8dd0-280091c4f4ad", + "name": "local", + "values": [ + { + "key": "openhim-core-url", + "value": "https://openhim-core:8080", + "enabled": true + }, + { + "key": "openhim-url", + "value": "https://openhim-core:5000", + "enabled": true + }, + { + "key": "opencr-url", + "value": "http://opencr:3000", + "enabled": true + }, + { + "key": "openhim-console-url", + "value": "http://openhim-console", + "enabled": true + } + ], + "_postman_variable_scope": "environment", + "_postman_exported_at": "2021-12-08T04:56:15.104Z", + "_postman_exported_using": "Postman/9.3.1-211203-1700" +} \ No newline at end of file diff --git a/.postman/run-tests.sh b/.postman/run-tests.sh new file mode 100755 index 00000000..758afe5c --- /dev/null +++ b/.postman/run-tests.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +docker-compose -f docker-compose.ports.yml up certgen + +docker-compose -f docker-compose.ports.yml up -d nginx openhim-core openhim-console mongo-db + +sleep 10 + +docker-compose -f docker-compose.ports.yml up openhim-config + +docker-compose -f docker-compose.ports.yml up -d shr-fhir opencr-fhir opencr-es kafka zookeeper + +sleep 30 + +docker-compose -f docker-compose.ports.yml up -d shr opencr + +sleep 30 + +docker-compose -f docker-compose.ports.yml logs shr opencr + +collections=( + 'https://www.getpostman.com/collections/46fd37386092a9f460e4' + 'https://www.getpostman.com/collections/4d682cbb222bb538d365' + 'https://www.getpostman.com/collections/4f2328a2ce056ff876e4' + 'https://www.getpostman.com/collections/0d397620f00804b00d75' +) + +for collection in ${collections[@]}; do + echo $collection + export POSTMAN_COLLECTION=$collection + docker-compose -f docker-compose.ports.yml up newman +done diff --git a/README.md b/README.md index 3fe5a321..0c4948af 100644 --- a/README.md +++ b/README.md @@ -1,65 +1,141 @@ -# sedish-haiti.org Demo Site +# SEDISH: The Haiti HIE +[![CI](https://github.com/I-TECH-UW/sedish-haiti.org/actions/workflows/main.yml/badge.svg)](https://github.com/I-TECH-UW/sedish-haiti.org/actions/workflows/main.yml) +## Components -Proof of Concept Environment Setup +### 1. iSantePlus EMR +### Links +https://github.com/IsantePlus/openmrs-distro-isanteplus +https://github.com/IsantePlus/docker-isanteplus-server -# Components +### 2. OpenCR +https://github.com/intrahealth/client-registry -## OpenMRS EMR -### Host URLs -- openmrs-server: http://localhost:8091/openmrs -- openmrs-db: jdbc:mysql://localhost:3308/openmrs -- fhir metadata: http://localhost:8091/openmrs/ws/fhir2/R4/metadata?_format=json +### 3. OpenHIM +http://openhim.org/docs/installation/docker -### Links -- https://github.com/pmanko/docker-openmrs-server +### 4. HAPI JPA Server +https://github.com/hapifhir/hapi-fhir-jpaserver-starter#deploy-with-docker-compose +https://hapifhir.io/hapi-fhir/docs/server_jpa/get_started.html + +### 5. Shared Health Record +https://github.com/i-tech-uw/shared-health-record -### Notes -- Server container requires restart after initial setup for some reason. -- Ran into issues with DB character set (https://talk.openmrs.org/t/ui-framework-error-while-attempting-to-access-registration-app/8734/6). - Had to specify characterset and collation in docker setup. -- To create demo patients, you can set the `OMRS_CONFIG_ADD_DEMO_DATA` variable in the `openmrs/refapp/openmrs-server.env` file - or set the `createDemoPatientsOnNextStartup` global property to the number of patients you want to create and restart the - container. +## Installation +### 1. Install Docker -### OpenCR +**Docker Engine:** -### iSantePlus EMR -- https://github.com/pmanko/isanteplus-docker/tree/shr +https://docs.docker.com/compose/install/ +**Docker Compose:** -### Local HAPI JPA Server -- https://hub.docker.com/r/bhits/hapi-fhir-jpaserver/ +https://docs.docker.com/compose/install/ -#### Notes -- Ran into issues with setting up Postgres due to DDL error for some table creation - the - generated DLL included "blob". WOndering if we can use this script which uses `oid`. Reverting to mysql f - for now. (solved: dialect set twice ::sigh:: ) -### OpenHIM -- http://openhim.org/docs/installation/docker +### 2. Clone the Sedish Repository -### SHR HAPI JPA Server -- https://hub.docker.com/_/postgres -- https://github.com/hapifhir/hapi-fhir-jpaserver-starter#deploy-with-docker-compose +```sh +git clone https://github.com/I-TECH-UW/sedish-haiti.org.git +``` -## Flink & Pipeline +### 3. Port-based Setup -### Host URLs -- flink console: https://localhost:3002 -- https://ci.apache.org/projects/flink/flink-docs-stable/ops/deployment/docker.html -- https://github.com/pmanko/beam-local-sync +**a) Pull all containers** +```sh +sudo docker-compose -f docker-compose.ports.yml pull +``` +**b) Start up Core Containers** +Generate self-signed certs: +```sh +sudo docker-compose -f docker-compose.ports.yml up certgen +``` -## Installation -1. Install Docker +```sh +sudo docker-compose -f docker-compose.ports.yml up -d nginx openhim-core openhim-console mongo-db +``` + +**c) Load Default OpenHIM Config** +First, make sure to choose and set a desired admin PW for OpenHIM that you'll use in all of the mediator configuration. + +You can set this Password with the "ADMIN_PW" env setting. + +```sh +sudo docker-compose -f docker-compose.ports.yml up openhim-config +``` + +**d) Access the OpenHIM Console** +You should now be able to access the OpenHIM console at https://localhost, or whatever IP address the server is running on. The OpenHIM console runs on ports 80 and 443. + +*Note: If you are using Chrome and get a certificate error, you can type `thisisunsafe` after clicking anywhere on the page to be able to proceed.* + +Make sure that the console is pointint to the correct `openhim-core` container. You should be able to access that container using `:8080/heartbeat`. You can configure this connection in `configs/openhim-console/ports.json`. + +**e) Modify OpenHIM settings as desired** +Log in to the console, and set the admin password to `openhim` (for development), or the password of your choice if you haven't done so earlier automatically. + +You can also set up Clients and Roles for the following systems: +- postman for testing +- each isanteplus instance that will connect to the HIE +- the Shared Health Record + +*Note: any changes to the OpenHIM console container might not show up until you disable/clear the browser cache. You can also disable the cache by opening Chrome dev tools with F12 and selecting the `disable cache` checkbox* + +**f) Start up Support Containers** +```sh +sudo docker-compose -f docker-compose.ports.yml up -d shr-fhir opencr-fhir opencr-es kafka zookeeper +``` + +**g) Configure Mediators** +Open, examine, and edit the following files as needed, to update the IP address for OpenHIM and set the right passwords: + - `./configs/opencr/config_ports.json` + - `./configs/shr/config_ports.json` +#### Start up Mediators + +```sh +sudo docker-compose -f docker-compose.ports.yml up -d shr opencr +``` +#### Start up iSantePlus +```sh +sudo docker-compose -f docker-compose.ports.yml up -d isanteplus +``` + +## 4. Testing and Validation +Setup a Postman environment and run tests from this workspace: https://www.postman.com/itechuw/workspace/isanteplus-pilot + +*Work in Progress* + + +## 5. Domain-based setup +Follow section 3.2, but use the main `docker-compose.yml` file, so without the `-f` flag. Also, set up certificate generation like so: +#### SSL Certificate Generation & Refresh +Modify the configuration for the `certbot` entry in the `docker-compose.yml` file to match server settings. See the [Certbot Docs](https://certbot.eff.org/) for more information. + +Here are example settings for the AWS setup: + +```yaml + certbot: + image: certbot/dns-route53 + container_name: certbot + entrypoint: "certbot certonly -n --agree-tos --email -d -d '*.' --dns-route53 --preferred-challenges=dns" + environment: + - AWS_ACCESS_KEY_ID + - AWS_SECRET_ACCESS_KEY + volumes: + - certs:/etc/letsencrypt + - letsencrypt:/var/lib/letsencrypt + networks: + - sedish +``` -2. Clone the repository +Then, run the following command: -3. Download https://www.dropbox.com/s/qp8zvaefuivqpcb/openmrs.zip?dl=1 and unzip into project directory +```sh +sudo docker-compose up certbot +``` -4. Use docker-compose to build and start containers +The certificates will be generated and provided to the other containers through a shared volume. -5. Clone local sync pipeline code diff --git a/compose.sh b/compose.sh deleted file mode 100644 index 6d8ca57f..00000000 --- a/compose.sh +++ /dev/null @@ -1,29 +0,0 @@ -composeFilePath=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) - -if [ "$1" == "init" ]; then - docker-compose -f "$composeFilePath"/docker-compose.yml up -d fhir es - - # Set up the openhim - # "$composeFilePath"/initiateReplicaSet.sh - - # Wait - sleep 100 - docker-compose -f "$composeFilePath"/docker-compose.yml up -d opencr - - -elif [ "$1" == "up" ]; then - docker-compose -f "$composeFilePath"/docker-compose.yml up -d fhir es - - # Wait - sleep 20 - docker-compose -f "$composeFilePath"/docker-compose.yml up -d opencr - -elif [ "$1" == "down" ]; then - docker-compose -f "$composeFilePath"/docker-compose.yml stop - -elif [ "$1" == "destroy" ]; then - docker-compose -f "$composeFilePath"/docker-compose.yml down -v - -else - echo "Valid options are: init, up, down, or destroy" -fi \ No newline at end of file diff --git a/configs/isanteplus/isanteplus.env b/configs/isanteplus/isanteplus.env new file mode 100644 index 00000000..ca05f481 --- /dev/null +++ b/configs/isanteplus/isanteplus.env @@ -0,0 +1,10 @@ +OMRS_JAVA_MEMORY_OPTS="-Xmx2048m -Xms1024m -XX:NewSize=128m" +OMRS_CONFIG_CONNECTION_SERVER=isanteplus-mysql +OMRS_CONFIG_CREATE_DATABASE_USER=false +OMRS_CONFIG_CREATE_TABLES=false +OMRS_CONFIG_ADD_DEMO_DATA=false +OMRS_CONFIG_CONNECTION_URL=jdbc:mysql://isanteplus-mysql:3306/openmrs?autoReconnect=true +OMRS_CONFIG_HAS_CURRENT_OPENMRS_DATABASE=true +OMRS_JAVA_SERVER_OPTS="-Dfile.encoding=UTF-8 -server -Djava.security.egd=file:/dev/./urandom -Djava.awt.headless=true -Djava.awt.headlesslib=true" +OMRS_CONFIG_CONNECTION_USERNAME=mysqluser +OMRS_CONFIG_CONNECTION_PASSWORD=mysqlpw \ No newline at end of file diff --git a/configs/isanteplus/mysql.cnf b/configs/isanteplus/mysql.cnf new file mode 100644 index 00000000..f8e8bf0c --- /dev/null +++ b/configs/isanteplus/mysql.cnf @@ -0,0 +1,62 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is a pre-configuration for Docker MySQL to allow BinLog and replication necessary configurations for Debezium to work +# see ./openmrs-compose.yaml +# REF: https://github.com/debezium/debezium/blob/master/debezium-connector-mysql/src/test/docker/server-gtids/my.cnf + +# For advice on how to change settings please see +# http://dev.mysql.com/doc/refman/5.7/en/server-configuration-defaults.html + +[mysqld] +# +# Remove leading # and set to the amount of RAM for the most important data +# cache in MySQL. Start at 70% of total RAM for dedicated server, else 10%. +# innodb_buffer_pool_size = 128M +# +# Remove leading # to turn on a very important data integrity option: logging +# changes to the binary log between backups. +# log_bin +# +# Remove leading # to set options mainly useful for reporting servers. +# The server defaults are faster for transactions and fast SELECTs. +# Adjust sizes as needed, experiment to find the optimal values. +# join_buffer_size = 128M +# sort_buffer_size = 2M +# read_rnd_buffer_size = 2M +skip-host-cache +skip-name-resolve +#datadir=/var/lib/mysql +#socket=/var/lib/mysql/mysql.sock +#secure-file-priv=/var/lib/mysql-files +user=mysql + +# Disabling symbolic-links is recommended to prevent assorted security risks +symbolic-links=0 + +#log-error=/var/log/mysqld.log +#pid-file=/var/run/mysqld/mysqld.pid + +# ---------------------------------------------- +# Enable the binlog for replication & CDC +# ---------------------------------------------- + +# Enable binary replication log and set the prefix, expiration, and log format. +# The prefix is arbitrary, expiration can be short for integration tests but would +# be longer on a production system. Row-level info is required for ingest to work. +# Server ID is required, but this will vary on production systems +server-id = 223344 +log_bin = mysql-bin +expire_logs_days = 1 +binlog_format = row \ No newline at end of file diff --git a/configs/nginx/healthcheck.sh b/configs/nginx/healthcheck.sh new file mode 100755 index 00000000..e9c669a0 --- /dev/null +++ b/configs/nginx/healthcheck.sh @@ -0,0 +1,8 @@ +#!/bin/bash +apt-get -y install newman + +if service nginx status; then + exit 0 +else + exit 1 +fi \ No newline at end of file diff --git a/configs/nginx/nginx.ports.conf b/configs/nginx/nginx.ports.conf new file mode 100644 index 00000000..28ebf72d --- /dev/null +++ b/configs/nginx/nginx.ports.conf @@ -0,0 +1,137 @@ +events { + worker_connections 4096; +} + +http { + fastcgi_read_timeout 1d; + client_max_body_size 1024M; + proxy_read_timeout 1d; + + # Point to self-generated certificates + ssl_certificate /etc/ssl/ports.crt; + ssl_certificate_key /etc/ssl/ports.key; + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_ciphers HIGH:!aNULL:!MD5; + + server { + listen 80 default_server; + + server_name _; + location / { + resolver 127.0.0.11 valid=30s; + + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + proxy_redirect off; + set $upstream openhim-console; + + proxy_pass http://$upstream; + } + } + + server { + listen 443 ssl; + server_name _; + + location / { + resolver 127.0.0.11 valid=30s; + + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + proxy_redirect off; + set $upstream openhim-console; + + proxy_pass http://$upstream; + } + } + + server { + listen 8080 ssl; + server_name _; + + location / { + resolver 127.0.0.11 valid=30s; + + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + proxy_redirect off; + set $upstream openhim-core; + + proxy_pass https://$upstream:8080; + } + } + + server { + listen 5000 ssl; + server_name _; + + location / { + resolver 127.0.0.11 valid=30s; + + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + proxy_redirect off; + + set $upstream openhim-core; + proxy_pass https://$upstream:5000; + } + } + + + server { + listen 5001 default_server; + server_name _; + + location / { + resolver 127.0.0.11 valid=30s; + + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + proxy_redirect off; + + set $upstream openhim-core; + proxy_pass http://$upstream:5001; + } + } + + + server { + listen 3000 ssl; + server_name _; + + location / { + resolver 127.0.0.11 valid=30s; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + proxy_redirect off; + + set $upstream opencr; + proxy_pass http://$upstream:3000; + } + } + + server { + listen 8000 ssl; + server_name _; + + location / { + resolver 127.0.0.11 valid=30s; + + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + proxy_redirect off; + + set $upstream isanteplus; + proxy_pass http://$upstream:8080; + } + } + +} diff --git a/configs/opencr/config_docker_template.json b/configs/opencr/config_ports.json similarity index 84% rename from configs/opencr/config_docker_template.json rename to configs/opencr/config_ports.json index 1ba77370..1b18e882 100644 --- a/configs/opencr/config_docker_template.json +++ b/configs/opencr/config_ports.json @@ -10,10 +10,10 @@ "mediator": { "api": { "username": "root@openhim.org", - "password": "haitiopenhim", - "apiURL": "https://core.sedish-haiti.org", + "password": "openhim", + "apiURL": "https://openhim-core:8080", "trustSelfSigned": true, - "urn": "" + "urn": "urn:mediator:haiti_clientregistry" }, "register": true }, @@ -36,19 +36,27 @@ "uri": "http://openclientregistry.org/fhir", "reportRelationship": "patientreport" }, - "matching": { - "tool": "elasticsearch" - }, "clients": [ { - "id": "openmrs_user", + "id": "openmrs", "displayName": "OpenMRS" }, + { + "id": "dhis2", + "displayName": "DHIS2" + }, { "id": "lims", "displayName": "Lab Info Management System" + }, + { + "id": "cr", + "displayName": "Client Registry" } ], + "matching": { + "tool": "elasticsearch" + }, "systems": { "CRBaseURI": "http://openclientregistry.org/fhir", "internalid": { @@ -73,7 +81,7 @@ } }, "sync": { - "lastFHIR2ESSync": "2021-10-14T18:31:24" + "lastFHIR2ESSync": "2021-12-08T04:54:09" }, "__comments": { "matching.tool": "this tells if the app should use mediator algorithms or elasticsearch algorithms for matching, two options mediator and elasticsearch" diff --git a/configs/opencr/mediator_ports.json b/configs/opencr/mediator_ports.json new file mode 100644 index 00000000..61047b85 --- /dev/null +++ b/configs/opencr/mediator_ports.json @@ -0,0 +1,179 @@ +{ + "urn": "urn:mediator:haiti_clientregistry", + "version": "1.1.0", + "name": "Client Registry", + "description": "Haiti Client Registry", + "config": { + "fhirServer": { + "username": "hapi", + "password": "hapi", + "baseURL": "http://opencr-fhir:8080/fhir" + }, + "elastic": { + "server": "http://es: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 or Get Resource Data From/To openCR", + "description": "Add or Get Resource Data From/To openCR", + "urlPattern": "/CR/fhir", + "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": [ + { + "type": "http", + "status": "enabled", + "forwardAuthHeader": false, + "name": "Add/Get Resources", + "secured": false, + "host": "opencr", + "port": 3000, + "path": "", + "pathTransform": "s/CR\/fhir/fhir/g", + "primary": true, + "username": "", + "password": "" + } + ], + "authType": "private", + "whitelist": [], + "type": "http", + "methods": [ + "GET", + "POST", + "PUT", + "DELETE" + ], + "updatedBy": { + "id": "5d5d94607329d74724442f67", + "name": "Super User" + } + } + ], + "endpoints": [ + { + "name": "Activate Client Registry", + "host": "opencr", + "path": "/", + "port": 3000, + "primary": true, + "forwardAuthHeader": false, + "status": "enabled", + "type": "http" + } + ] +} \ No newline at end of file diff --git a/configs/openhim-console/ports.json b/configs/openhim-console/ports.json new file mode 100644 index 00000000..9218cc4b --- /dev/null +++ b/configs/openhim-console/ports.json @@ -0,0 +1,13 @@ +{ + "version": "1.13.2", + "minimumCoreVersion": "5.2.0", + "protocol": "https", + "host": "localhost", + "port": 8080, + "title": "SEDISH Admin Console V2", + "footerTitle": "SEDISH OpenHIM Administration Console", + "footerPoweredBy": "Powered by OpenHIM", + "loginBanner": "", + "mediatorLastHeartbeatWarningSeconds": 60, + "mediatorLastHeartbeatDangerSeconds": 120 +} diff --git a/configs/openhim-core/initial-config.json b/configs/openhim-core/initial-config.json new file mode 100644 index 00000000..7492e843 --- /dev/null +++ b/configs/openhim-core/initial-config.json @@ -0,0 +1,234 @@ +{ + "Users": [ + { + "groups": [ + "admin" + ], + "firstname": "Super", + "surname": "User", + "email": "root@openhim.org", + "passwordAlgorithm": "sha512", + "passwordHash": "3ffa5a209fad0151388a1fc0eb2d93cfb95648c38a79ee19b8c19811ee9256a49dfc929afd1f81f35533553044d2fc19921939f9b53ef5024f9ae0486aecca05", + "passwordSalt": "b9bce89f512c986981512a91a9eeb5ef", + "expiry": null, + "locked": false, + "tokenType": null, + "token": null + } + ], + "Clients": [ + { + "roles": [ + "testing" + ], + "clientID": "postman", + "name": "postman", + "passwordAlgorithm": "sha512", + "passwordSalt": "abf377048344f01c31aad81dca89b7e1", + "passwordHash": "6e9de40590aae84ba19384eaab5bb6c3c43b3577fdf9c97217b3c1e52cccd2819dcbdb097a2ac8ba63dedc0c8604dedd42e44b3725f24d7371450ce17d89de46" + }, + { + "roles": [ + "emr" + ], + "clientID": "isanteplus-demo", + "name": "isanteplus-demo", + "passwordAlgorithm": "sha512", + "passwordSalt": "aab9f6187c1af81b1e5c2d7c13e73df2", + "passwordHash": "19e6ebce33412c769068ffd429116e79ef991961b94c00cb63660d6d0bce3c8b9a641c639823299932705f29800a7d08f8e459c79867c9de4210a21adb4ff718" + }, + { + "roles": [ + "mediator" + ], + "clientID": "shr", + "name": "shr", + "passwordAlgorithm": "sha512", + "passwordSalt": "569762b1be521ecde76266c49ab0641a", + "passwordHash": "ee1799f8c9a50849213810c9986d292eac51c2ed7d7d6baf7288f9d42c6c2939e9e0addbc7111f53a497d8db738a375148e96bdc446a595223f1cfa4910e1b0f" + }, + { + "roles": [ + "emr" + ], + "clientID": "isanteplus", + "name": "isanteplus", + "passwordAlgorithm": "sha512", + "passwordSalt": "198a0f8b3efc68237911dc4b3b877a6b", + "passwordHash": "aff7de981ecf1af8a41a0a17718d613dc19728c5fc201fdede8eee49e34708e31dd19da974f32063145f982a54e0134c8070b4dc7eb9928733d3cd9d5037ed6d" + } + ], + "Channels": [ + { + "methods": [ + "GET", + "POST", + "DELETE", + "PUT" + ], + "type": "http", + "allow": [ + "testing", + "emr", + "lis", + "mediator" + ], + "whitelist": [], + "authType": "private", + "matchContentTypes": [], + "properties": [], + "txViewAcl": [], + "txViewFullAcl": [], + "txRerunAcl": [], + "status": "enabled", + "rewriteUrls": false, + "addAutoRewriteRules": true, + "autoRetryEnabled": false, + "autoRetryPeriodMinutes": 60, + "requestBody": true, + "responseBody": true, + "name": "Add or Get Resource Data From/To openCR", + "description": "Add or Get Resource Data From/To openCR", + "urlPattern": "/CR/fhir", + "matchContentRegex": null, + "matchContentXpath": null, + "matchContentValue": null, + "matchContentJson": null, + "pollingSchedule": null, + "tcpHost": null, + "tcpPort": null, + "rewriteUrlsConfig": [], + "alerts": [], + "routes": [ + { + "type": "http", + "status": "enabled", + "forwardAuthHeader": false, + "name": "Add/Get Resources", + "secured": false, + "host": "opencr", + "port": 3000, + "path": "", + "pathTransform": "s/CR\\/fhir/fhir/g", + "primary": true, + "username": "", + "password": "" + } + ], + "updatedBy": { + "id": "61b027f30c02be0014fdae47", + "name": "Super User" + }, + "priority": 1 + }, + { + "methods": [ + "GET", + "POST", + "PUT", + "PATCH" + ], + "type": "http", + "allow": [ + "testing", + "emr", + "lis", + "mediator" + ], + "whitelist": [], + "authType": "private", + "matchContentTypes": [], + "properties": [], + "txViewAcl": [], + "txViewFullAcl": [], + "txRerunAcl": [], + "status": "enabled", + "rewriteUrls": false, + "addAutoRewriteRules": true, + "autoRetryEnabled": false, + "autoRetryPeriodMinutes": 60, + "requestBody": true, + "responseBody": true, + "name": "SHR - FHIR Passthrough", + "description": "Get or Post a new FHIR Resource to the SHR", + "urlPattern": "^/SHR/fhir.*$", + "routes": [ + { + "type": "http", + "status": "enabled", + "forwardAuthHeader": false, + "name": "SHR - Get/Create/Update Resource", + "secured": false, + "host": "shr", + "port": 3000, + "path": "", + "pathTransform": "s/SHR\\/fhir/fhir/g", + "primary": true, + "username": "", + "password": "" + } + ], + "priority": 1, + "alerts": [], + "rewriteUrlsConfig": [], + "updatedBy": { + "id": "61b027f30c02be0014fdae47", + "name": "Super User" + } + }, + { + "methods": [ + "GET", + "POST", + "PUT" + ], + "type": "http", + "allow": [ + "testing", + "emr", + "lis", + "mediator" + ], + "whitelist": [], + "authType": "private", + "matchContentTypes": [], + "properties": [], + "txViewAcl": [], + "txViewFullAcl": [], + "txRerunAcl": [], + "status": "enabled", + "rewriteUrls": false, + "addAutoRewriteRules": true, + "autoRetryEnabled": false, + "autoRetryPeriodMinutes": 60, + "name": "SHR - Get/Update IPS", + "requestBody": true, + "responseBody": true, + "description": "Get or Update the Internaional Patient Summary Bundle from the SHR", + "urlPattern": "^/SHR/fhir/ips.*$", + "routes": [ + { + "type": "http", + "status": "enabled", + "forwardAuthHeader": false, + "name": "SHR - Get IPS", + "secured": false, + "host": "shr", + "port": 3000, + "path": "", + "pathTransform": "s/SHR\\/fhir\\/ips/ips/g", + "primary": true, + "username": "", + "password": "" + } + ], + "alerts": [], + "rewriteUrlsConfig": [], + "priority": 2, + "updatedBy": { + "id": "61b027f30c02be0014fdae47", + "name": "Super User" + } + } + ] +} \ No newline at end of file diff --git a/configs/shr/config_docker_template.json b/configs/shr/config_docker_template.json deleted file mode 100644 index 0d96b3c5..00000000 --- a/configs/shr/config_docker_template.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "auth": { - "secret": "3084e343-71bc-4247-86e4-ea210af89c28", - "tokenDuration": 5400 - }, - "app": { - "port": 3000, - "installed": false - }, - "mediator": { - "api": { - "username": "root@openhim.org", - "password": "haitiopenhim", - "apiURL": "https://core.sedish-haiti.org", - "trustSelfSigned": true, - "urn": "" - } - }, - "fhirServer": { - "username": "shr", - "password": "shr123", - "mpiURL": "https://sedish-haiti.org:5000/CR/fhir", - "baseURL": "http://shr-fhir/fhir" - }, - - "structureDefinition": { - "uri": "http://openclientregistry.org/fhir", - "reportRelationship": "patientreport" - }, - "clients": [ - { - "id": "openmrs_user", - "displayName": "OpenMRS" - }, - { - "id": "lims", - "displayName": "Lab Info Management System" - } - ] -} diff --git a/configs/shr/config_ports.json b/configs/shr/config_ports.json new file mode 100644 index 00000000..4933752c --- /dev/null +++ b/configs/shr/config_ports.json @@ -0,0 +1,25 @@ +{ + "app": { + "port": 3000 + }, + "mediator": { + "api": { + "username": "root@openhim.org", + "password": "openhim", + "apiURL": "https://openhim-core:8080", + "trustSelfSigned": true, + "urn": "urn:mediator:haiti_sharedhealthrecord" + }, + "client": { + "id": "shr-client", + "password": "shr-client" + } + }, + "fhirServer": { + "mpiURL": "https://openhim-core:5000/CR/fhir", + "baseURL": "http://shr-fhir:8080/fhir" + }, + "taskRunner": { + "brokers" : ["kafka:9092"] + } +} diff --git a/configs/shr/mediator_ports.json b/configs/shr/mediator_ports.json new file mode 100644 index 00000000..f2d984b1 --- /dev/null +++ b/configs/shr/mediator_ports.json @@ -0,0 +1,151 @@ +{ + "urn": "urn:mediator:haiti_sharedhealthrecord", + "version": "1.1.0", + "name": "Haiti SHR", + "description": "Haiti Shared Health Record", + "defaultChannelConfig": [ + { + "methods": [ + "GET", + "POST", + "PUT", + "PATCH" + ], + "type": "http", + "whitelist": [], + "authType": "private", + "matchContentTypes": [], + "properties": [], + "txViewAcl": [], + "txViewFullAcl": [], + "txRerunAcl": [], + "status": "enabled", + "rewriteUrls": false, + "addAutoRewriteRules": true, + "autoRetryEnabled": false, + "autoRetryPeriodMinutes": 60, + "requestBody": true, + "responseBody": true, + "name": "SHR - FHIR Passthrough", + "description": "Get or Post a new FHIR Resource to the SHR", + "urlPattern": "^/SHR/fhir.*$", + "routes": [ + { + "type": "http", + "status": "enabled", + "forwardAuthHeader": false, + "name": "SHR - Get/Create/Update Resource", + "secured": false, + "host": "shr", + "port": 3000, + "path": "", + "pathTransform": "s/SHR\\/fhir/fhir/g", + "primary": true, + "username": "", + "password": "" + } + ], + "priority": 1 + }, + { + "methods": [ + "GET", + "POST", + "PUT" + ], + "type": "http", + "whitelist": [], + "authType": "private", + "matchContentTypes": [], + "properties": [], + "txViewAcl": [], + "txViewFullAcl": [], + "txRerunAcl": [], + "status": "enabled", + "rewriteUrls": false, + "addAutoRewriteRules": true, + "autoRetryEnabled": false, + "autoRetryPeriodMinutes": 60, + "name": "SHR - Get/Update IPS", + "requestBody": true, + "responseBody": true, + "description": "Get or Update the Internaional Patient Summary Bundle from the SHR", + "urlPattern": "^/SHR/fhir/ips.*$", + "routes": [ + { + "type": "http", + "status": "enabled", + "forwardAuthHeader": false, + "name": "SHR - Get IPS", + "secured": false, + "host": "shr", + "port": 3000, + "path": "", + "pathTransform": "s/SHR\\/fhir\\/ips/ips/g", + "primary": true, + "username": "", + "password": "" + } + ], + "alerts": [], + "rewriteUrlsConfig": [], + "priority": 2 + }, + { + "methods": [ + "GET", + "POST", + "PUT" + ], + "type": "http", + "whitelist": [], + "authType": "private", + "matchContentTypes": [], + "properties": [], + "txViewAcl": [], + "txViewFullAcl": [], + "txRerunAcl": [], + "status": "enabled", + "rewriteUrls": false, + "addAutoRewriteRules": true, + "autoRetryEnabled": false, + "autoRetryPeriodMinutes": 60, + "name": "SHR - Laboratory", + "requestBody": true, + "responseBody": true, + "description": "Get or Update the Laboratory Order Bundle in the SHR", + "urlPattern": "^/SHR/fhir/ips.*$", + "routes": [ + { + "type": "http", + "status": "enabled", + "forwardAuthHeader": false, + "name": "SHR - Laboratory", + "secured": false, + "host": "shr", + "port": 3000, + "path": "", + "pathTransform": "s/SHR\\/fhir\\/ips/ips/g", + "primary": true, + "username": "", + "password": "" + } + ], + "alerts": [], + "rewriteUrlsConfig": [], + "priority": 3 + } + ], + "endpoints": [ + { + "name": "SHR Endpoint", + "host": "shr", + "path": "/", + "port": 3000, + "primary": true, + "forwardAuthHeader": false, + "status": "enabled", + "type": "http" + } + ] +} \ No newline at end of file diff --git a/docker-compose.ports.yml b/docker-compose.ports.yml new file mode 100644 index 00000000..a6701852 --- /dev/null +++ b/docker-compose.ports.yml @@ -0,0 +1,278 @@ +version: '3.8' + +## Port Assignments: +## See .env file + +## Container debugging: +# 1. append the following lines to desired container +# 2. boot up the container using `docker-compose up -d` +# 3 run `docker exec -it bash` to start interactive shell +# +# tty: true +# stdin_open: true +# entrypoint: bash + +## Utility for booting up placeholder page: +# `docker run --hostname openhim-placeholder --network shared-health-record_sedish --name openhim-placeholder -e MESSAGE=OPENHIM-PLACEHOLDER -e PORT=3000 -d docker.io/sroze/landing-page:latest` + +services: + ### + # nginx reverse proxy + # TODO: set up to use non-root user. See https://www.rockyourcode.com/run-docker-nginx-as-non-root-user/ + ### + nginx: + image: nginx:latest + container_name: nginx + hostname: nginx + restart: unless-stopped + ports: + - "80:80" + - "443:443" + - "8080:8080" # OpenHIM Core API + - "5000:5000" # OpenHIM HTTPS + - "5001:5001" # OpenHIM HTTP + - "3000:3000" # OpenCR + - "8000:8000" # iSantePlus + volumes: + - ./configs/nginx/nginx.ports.conf:/etc/nginx/nginx.conf + - nginx-data:/var/www + - ./configs/nginx/healthcheck.sh:/healthcheck.sh + - certs:/etc/ssl/ + networks: + - sedish + healthcheck: + test: [ "CMD-SHELL", "/healthcheck.sh" ] + interval: 10s + timeout: 1m + retries: 100 + start_period: 3m + certgen: + image: magnitus/certificate-generator:latest + environment: + COUNTRY: US + STATE: WA + CITY: SEATTLE + ORGANIZATION: I-TECH-UW + DEPARTMENT: DIGI + EMAIL: cert@sedish-heaiti.org + DOMAINS: dev.mydomain.com;test.mydomain.com;localhost + CERTIFICATE_DURATION: 11095 + OUTPUT_CERTIFICATE_INFO: "true" + KEY_FILE: "ports.key" + CERTIFICATE_FILE: "ports.crt" + volumes: + - certs:/opt/output + ### + # OpenCR + ### + opencr: + platform: linux/amd64 + container_name: opencr + hostname: opencr + image: intrahealth/opencr:latest + restart: unless-stopped + environment: + - NODE_ENV=docker + - HAPI_FHIR_URL=http://opencr-fhir:8080/fhir/metadata + networks: + - sedish + volumes: + - ./configs/opencr/config_ports.json:/src/server/config/config_docker.json + - ./configs/opencr/mediator_ports.json:/src/server/config/mediator.json + - ./configs/opencr/decisionRules.json:/src/server/config/decisionRules.json + + opencr-fhir: + image: "hapiproject/hapi:latest" + container_name: opencr-fhir + hostname: opencr-fhir + restart: unless-stopped + volumes: + - cr-data:/data/hapi + networks: + - sedish + + opencr-es: + container_name: es + hostname: es + image: intrahealth/elasticsearch:latest + restart: unless-stopped + environment: + - node.name=es01 + - discovery.type=single-node + - bootstrap.memory_lock=true + - "ES_JAVA_OPTS=-Xms512m -Xmx512m" + volumes: + - esdata:/usr/share/elasticsearch/data + networks: + - sedish + + ### + # SHR + ### + shr: + container_name: shr + hostname: shr + image: ghcr.io/i-tech-uw/shared-health-record:v0.4.0 + restart: unless-stopped + environment: + - NODE_ENV=docker + networks: + - sedish + volumes: + - ./configs/shr/config_ports.json:/app/config/config_docker.json + - ./configs/shr/mediator_ports.json:/app/config/mediator_docker.json + + shr-fhir: + image: "hapiproject/hapi:latest" + container_name: shr-fhir + hostname: shr-fhir + restart: unless-stopped + volumes: + - hapi-data:/data/hapi + networks: + - sedish + + #### + # Kafka + ### + zookeeper: + image: 'bitnami/zookeeper:latest' + hostname: zookeeper + environment: + - ALLOW_ANONYMOUS_LOGIN=yes + networks: + - sedish + kafka: + image: 'bitnami/kafka:latest' + hostname: kafka + container_name: kafka + environment: + - KAFKA_BROKER_ID=1 + - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092 + - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092 + - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181 + - ALLOW_PLAINTEXT_LISTENER=yes + - KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE=true + volumes: + - kafka-data:/bitnami/kafka + depends_on: + - zookeeper + networks: + - sedish + + ### + # iSantePlus + ### + isanteplus-demo: + container_name: isanteplus-demo + hostname: isanteplus-demo + image: ghcr.io/isanteplus/docker-isanteplus-server:latest + restart: unless-stopped + env_file: + - ./configs/isanteplus/isanteplus.env + volumes: + - isanteplus-data:/openmrs/data + networks: + - sedish + + isanteplus-mysql: + container_name: isanteplus-mysql + hostname: isanteplus-mysql + image: ghcr.io/isanteplus/docker-isanteplus-db:latest + restart: unless-stopped + environment: + - MYSQL_DATABASE=openmrs + - MYSQL_ROOT_PASSWORD=debezium + - MYSQL_USER=mysqluser + - MYSQL_PASSWORD=mysqlpw + - MYSQL_ROOT_HOST=% # Allow docker containers to connect to mysql + volumes: + - ./configs/isanteplus/mysql.cnf:/etc/mysql/conf.d/custom.cnf # mysql config preconfigured to allow binlog/debezium + - isanteplus-mysql-data:/var/lib/mysql + + ### + # OpenHIM + ### + openhim-core: + container_name: openhim-core + image: jembi/openhim-core:latest + restart: unless-stopped + environment: + mongo_url: "mongodb://mongo-db/openhim" + mongo_atnaUrl: "mongodb://mongo-db/openhim" + NODE_ENV: "development" + healthcheck: + test: "curl -sSk https://localhost:8080/heartbeat || exit 1" + interval: 2m + timeout: 1m + retries: 5 + networks: + - sedish + + openhim-console: + container_name: openhim-console + image: jembi/openhim-console:latest + restart: unless-stopped + volumes: + - ./configs/openhim-console/ports.json:/usr/share/nginx/html/config/default.json + healthcheck: + test: "curl -sS http://openhim-console || exit 1" + interval: 30s + timeout: 30s + retries: 3 + networks: + - sedish + openhim-config: + container_name: openhim-config + image: ghcr.io/i-tech-uw/openhim-config:v0.0.0 + volumes: + - ./configs/openhim-core/initial-config.json:/app/test-openhim-config.json + networks: + - sedish + environment: + - INITIAL_PW=openhim-password + - ADMIN_PW=$ADMIN_PW + - API_URL=https://openhim-core:8080 + mongo-db: + container_name: mongo-db + image: mongo:3.4 + volumes: + - "mongo-data:/data/db" + restart: unless-stopped + networks: + - sedish + + # Newman Tests + newman: + image: postman/newman + volumes: + - ./.postman:/.postman + entrypoint: newman run $POSTMAN_COLLECTION -e /.postman/postman_env.ci.json --insecure --timeout-request 20000 --delay-request 500 + networks: + - sedish + +volumes: + esdata: + driver: local + isanteplus-data: + driver: local + isanteplus-mysql-data: + driver: local + mongo-data: + driver: local + shr: + driver: local + hapi-data: + driver: local + cr-data: + driver: local + certs: + driver: local + letsencrypt: + driver: local + nginx-data: + driver: local + kafka-data: + driver: local +networks: + sedish: diff --git a/docker-compose.yml b/docker-compose.yml index f6453376..c415b330 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,7 +19,6 @@ services: ### # nginx reverse proxy # TODO: set up to use non-root user. See https://www.rockyourcode.com/run-docker-nginx-as-non-root-user/ - # TODO: Run letsencrypt as docker container: https://hub.docker.com/r/certbot/certbot/ ### nginx: image: nginx:latest @@ -55,8 +54,7 @@ services: opencr: container_name: opencr hostname: opencr - #image: intrahealth/opencr:latest - image: intrahealth/opencr:fdb3d44 + image: intrahealth/opencr:latest restart: unless-stopped environment: - NODE_ENV=docker @@ -67,9 +65,6 @@ services: - ./configs/opencr/config_docker_template.json:/src/server/config/config_docker.json - ./configs/opencr/mediator.json:/src/server/config/mediator.json - ./configs/opencr/decisionRules.json:/src/server/config/decisionRules.json - - /home/ubuntu/local/letsencrypt/archive/sedish-haiti.org/privkey3.pem:/src/server/serverCertificates/server_key.pem - - /home/ubuntu/local/letsencrypt/archive/sedish-haiti.org/fullchain3.pem:/src/server/serverCertificates/server_cert.pem - opencr-fhir: image: "hapiproject/hapi:latest" container_name: opencr-fhir