diff --git a/.gitea/workflows/ci-cd.yaml b/.gitea/workflows/ci-cd.yaml new file mode 100644 index 0000000..58170fc --- /dev/null +++ b/.gitea/workflows/ci-cd.yaml @@ -0,0 +1,283 @@ +name: FHIR IG CI/CD Pipeline + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main ] + +env: + REGISTRY: git.dghs.gov.bd # Replace with your Gitea instance + IMAGE_NAME: gitadmin/bd-core-fhir-ig # Replace with your image name + +jobs: + build-ig: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 # Full history for proper IG building + + - name: Install Docker CLI + run: | + apt-get update + apt-get install -y docker.io + docker --version + + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + install: true + + + - name: Build FHIR IG (Copy In/Out) + run: | + echo "Building FHIR IG using copy approach..." + + # Create a container (don't start yet) + CONTAINER_ID=$(docker create \ + hl7fhir/ig-publisher-base:latest \ + /bin/bash -c "cp -r /home/publisher/ig /tmp/build && cd /tmp/build && _updatePublisher.sh -y && _genonce.sh") + + echo "Container ID: $CONTAINER_ID" + + # Copy all source files into the container + docker cp $(pwd)/. $CONTAINER_ID:/home/publisher/ig/ + + # Start and wait for completion + docker start -a $CONTAINER_ID + EXIT_CODE=$? + + # Copy outputs back + echo "Copying outputs from container..." + docker cp $CONTAINER_ID:/tmp/build/output ./output || echo "Warning: No output directory" + docker cp $CONTAINER_ID:/tmp/build/fsh-generated ./fsh-generated || echo "No FSH generated files" + docker cp $CONTAINER_ID:/tmp/build/input-cache ./input-cache || echo "No input-cache" + docker cp $CONTAINER_ID:/tmp/build/temp ./temp || echo "No temp directory" + + # Show container logs if failed + if [ $EXIT_CODE -ne 0 ]; then + echo "Build failed, showing container logs:" + docker logs $CONTAINER_ID + fi + + # Cleanup + docker rm $CONTAINER_ID + + # Verify + if [ ! -f "output/index.html" ]; then + echo "ERROR: Build failed - no index.html" + exit 1 + fi + + echo "✅ Build successful!" + + - name: Verify IG Output + run: | + ls -la output/ + if [ ! -f "output/index.html" ]; then + echo "ERROR: IG build failed - no index.html found" + exit 1 + fi + echo "IG build successful!" + + - name: Login to Gitea Container Registry + if: github.ref == 'refs/heads/main' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ gitea.actor }} + password: ${{ secrets.ACCESS_TOKEN_GITEA }} + + - name: Extract metadata + if: github.ref == 'refs/heads/main' + id: meta + uses: docker/metadata-action@v4 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=sha,prefix={{branch}}- + type=raw,value=latest,enable={{is_default_branch}} + + - name: Build and push Docker image + if: github.ref == 'refs/heads/main' + uses: docker/build-push-action@v6 + with: + context: . + file: ./Dockerfile.serve + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + # cache-from: type=gha + # cache-to: type=gha,mode=max + + deploy: + needs: build-ig + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Deploy to server + uses: appleboy/ssh-action@v1.0.3 + env: + REGISTRY: ${{ env.REGISTRY }} + IMAGE_NAME: ${{ env.IMAGE_NAME }} + IMAGE_TAG: latest + with: + host: ${{ secrets.DEPLOY_HOST }} + username: ${{ secrets.DEPLOY_USER }} + password: ${{ secrets.DEPLOY_PASSWORD }} + port: ${{ secrets.DEPLOY_PORT || 22 }} + envs: REGISTRY,IMAGE_NAME,IMAGE_TAG + script: | + # Create deployment directory if it doesn't exist + mkdir -p /opt/fhir-ig + cd /opt/fhir-ig + + # Create docker-compose.prod.yml + cat > docker-compose.prod.yml << EOF + + services: + fhir-ig: + image: \${REGISTRY}/\${IMAGE_NAME}:\${IMAGE_TAG:-latest} + container_name: fhir-ig-app + restart: unless-stopped + ports: + - "80:80" + environment: + - NODE_ENV=production + networks: + - fhir-ig-network + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + volumes: + - fhir-ig-logs:/var/log/nginx + + networks: + fhir-ig-network: + driver: bridge + + volumes: + fhir-ig-logs: + driver: local + EOF + + # Create deployment script + cat > deploy.sh << 'DEPLOY_SCRIPT' + #!/bin/bash + set -e + + # Configuration + COMPOSE_FILE="docker-compose.prod.yml" + SERVICE_NAME="fhir-ig" + BACKUP_DIR="/opt/backups/fhir-ig" + LOG_FILE="/var/log/fhir-ig-deploy.log" + + # Create directories + mkdir -p "$BACKUP_DIR" + mkdir -p "$(dirname "$LOG_FILE")" + + # Logging function + log() { + echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE" + } + + log "Starting deployment of BD Core FHIR IG..." + log "Registry: $REGISTRY" + log "Image: $IMAGE_NAME" + log "Tag: $IMAGE_TAG" + + # Login to registry + echo "$GITEA_PASSWORD" | docker login $REGISTRY -u "$GITEA_USERNAME" --password-stdin + + # Backup current container if it exists + if docker compose -f "$COMPOSE_FILE" ps --services --filter "status=running" | grep -q "$SERVICE_NAME"; then + log "Creating backup of current deployment..." + BACKUP_FILE="$BACKUP_DIR/backup-$(date +%Y%m%d-%H%M%S).tar.gz" + docker compose -f "$COMPOSE_FILE" exec -T "$SERVICE_NAME" tar -czf - -C /usr/share/nginx/html . > "$BACKUP_FILE" 2>/dev/null || log "Backup failed, continuing..." + fi + + # Set environment variables for docker compose + export REGISTRY="$REGISTRY" + export IMAGE_NAME="$IMAGE_NAME" + export IMAGE_TAG="$IMAGE_TAG" + + # Pull the latest image + log "Pulling latest image: $REGISTRY/$IMAGE_NAME:$IMAGE_TAG..." + docker pull "$REGISTRY/$IMAGE_NAME:$IMAGE_TAG" + + # docker pull "\${REGISTRY}/\${IMAGE_NAME}:\${IMAGE_TAG}" + + # Stop and remove old container + log "Stopping old container..." + docker compose -f "$COMPOSE_FILE" down || log "No existing container to stop" + + # Start new container + log "Starting new container..." + docker compose -f "$COMPOSE_FILE" up -d + + # Wait for container to be healthy + # log "Waiting for container to become healthy..." + # timeout=120 + # elapsed=0 + # healthy=false + + # while [ $elapsed -lt $timeout ]; do + # if docker compose -f "$COMPOSE_FILE" ps --format json | grep -q '"Health":"healthy"'; then + # log "Container is healthy!" + # healthy=true + # break + # fi + # sleep 5 + # elapsed=$((elapsed + 5)) + # log "Waiting... ($elapsed/$timeout seconds)" + # done + + # if [ "$healthy" = false ]; then + # log "ERROR: Container failed to become healthy within $timeout seconds" + # docker compose -f "$COMPOSE_FILE" logs --tail=50 + # log "Rolling back..." + # docker compose -f "$COMPOSE_FILE" down + # exit 1 + # fi + + # Cleanup old images (keep last 3 versions) + log "Cleaning up old images..." + docker images "\${REGISTRY}/\${IMAGE_NAME}" --format "table {{.Repository}}:{{.Tag}}\t{{.CreatedAt}}" | tail -n +2 | sort -k2 -r | tail -n +4 | awk '{print $1}' | xargs -r docker rmi || log "No old images to clean" + + # Cleanup old backups (keep only last 5) + log "Cleaning up old backups..." + ls -t "$BACKUP_DIR"/backup-*.tar.gz 2>/dev/null | tail -n +6 | xargs -r rm || log "No old backups to clean" + + log "Deployment completed successfully!" + log "🌐 Service available at: http://$(hostname -I | awk '{print $1}')" + + # Display final status + docker compose -f "$COMPOSE_FILE" ps + DEPLOY_SCRIPT + + # Make deploy script executable + chmod +x deploy.sh + + # Set registry credentials + export GITEA_USERNAME="${{ gitea.actor }}" + export GITEA_PASSWORD="${{ secrets.ACCESS_TOKEN_GITEA }}" + + # Execute deployment + ./deploy.sh \ No newline at end of file diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml deleted file mode 100644 index b2a378e..0000000 --- a/.gitea/workflows/deploy.yaml +++ /dev/null @@ -1,23 +0,0 @@ -name: Deploy on production -on: - push: - branches: - - main - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - name: Deploy by SSH to production environment - uses: appleboy/ssh-action@master - with: - host: ${{ secrets.PRODUCTION_SSH_HOST }} - username: ${{ secrets.PRODUCTION_SSH_USERNAME }} - password: ${{ secrets.PRODUCTION_SSH_PASSWORD }} - port: ${{ secrets.PRODUCTION_SSH_PORT }} - script: | - cd /home/mishealth/BD-Core-FHIR-IG - git pull origin main - docker run --rm -v $(pwd):/home/publisher/ig hl7fhir/ig-publisher-base:latest /home/publisher/ig/_genonce.sh - rsync -av output/ /var/www/html - sudo systemctl restart nginx \ No newline at end of file diff --git a/Dockerfile.serve b/Dockerfile.serve new file mode 100644 index 0000000..26d6268 --- /dev/null +++ b/Dockerfile.serve @@ -0,0 +1,41 @@ +# Multi-stage build for serving FHIR IG output +FROM nginx:alpine + +# Copy the built IG output to nginx html directory +# (Uncomment and adjust the path if needed) +COPY output/ /usr/share/nginx/html/ + +# Copy custom nginx configuration +COPY nginx.conf /etc/nginx/nginx.conf + +# Create a non-root user for security +RUN addgroup -g 1001 -S nginx-user && \ + adduser -S -D -H -u 1001 -h /var/cache/nginx -s /sbin/nologin -G nginx-user -g nginx-user nginx-user + +# Set proper permissions for Nginx directories +RUN chown -R nginx-user:nginx-user /usr/share/nginx/html && \ + chown -R nginx-user:nginx-user /var/cache/nginx && \ + chown -R nginx-user:nginx-user /var/log/nginx && \ + chown -R nginx-user:nginx-user /etc/nginx/conf.d + +# Fix Nginx PID permission issue +RUN mkdir -p /var/cache/nginx/run && \ + chown -R nginx-user:nginx-user /var/cache/nginx/run + +# Update nginx.conf to point PID to writable location +# Ensure your nginx.conf has: +# pid /var/cache/nginx/run/nginx.pid; + +# Switch to non-root user +USER nginx-user + +# Health check +# HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ +# CMD curl -f http://localhost/ || exit 1 + +EXPOSE 80 + +# Start Nginx in foreground +# CMD ["nginx", "-g", "daemon off;"] + +CMD ["nginx", "-g", "daemon off;", "-c", "/etc/nginx/nginx.conf"] \ No newline at end of file diff --git a/README.md b/README.md index 3ddbd7b..28ff3e1 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,39 @@ -# sample-ig -A sample, template-driven implementation guide that provides a starting environment to use a base for defining new IGs +# Bangladesh Core FHIR Implementation Guide (BD-Core-FHIR-IG) +The **Bangladesh Core FHIR Implementation Guide (IG)** defines the national baseline for health data exchange in Bangladesh. +It ensures that health information systems can share and understand data consistently, supporting the goals of the **Bangladesh Digital Health Blueprint**. -See https://build.fhir.org/ig/FHIR/sample-ig/ +👉 Published IG: [https://fhir.dghs.gov.bd](https://fhir.dghs.gov.bd) +--- -## FHIR Foundation Project Statement +## Purpose -* Maintainers: Grahame Grieve / Lloyd Mckenzie -* Issues / Discussion: Use github issues -* License: Creative Commons Public Domain -* Contribution Policy: Make PRs. PRs have to build ok with the latest IG Publisher -* Security Information: There should be no security issues here - it's all static content. Can report issues with the standard github repotring method +- Provide a **national reference** for stakeholders building digital health solutions. +- Ensure alignment with **international FHIR standards**, making future **cross-border health information exchange** possible. +- Promote **patient-centered data exchange**, reducing duplication and improving continuity of care. + +--- + +## Using This IG + +- **For Health System Developers:** + Use the defined **profiles, value sets, and code systems** in this IG when designing or integrating digital health applications. This ensures interoperability with other systems in Bangladesh and beyond. + +- **For Policymakers and Implementers:** + Treat this IG as the **standard reference** for system requirements, data flows, and exchange formats when planning or evaluating digital health projects. + +- **For Vendors and Partners:** + Align your solutions with this IG to ensure **compliance with national standards** and **seamless integration** with Bangladesh’s digital health ecosystem. + +--- + +## Contributions + +Feedback and contributions are welcome. Issues can be raised through this repository to suggest improvements or alignments. + +--- + +## License + +This project is released under the [Creative Commons Public Domain License](https://creativecommons.org/publicdomain/). diff --git a/ig.ini b/ig.ini index dca5267..ccecc2d 100644 --- a/ig.ini +++ b/ig.ini @@ -2,7 +2,7 @@ # ini file for the Implementation Guide publisher # see comments below for instructions -ig = input/myig.xml +ig = fsh-generated/resources/ImplementationGuide-bd.fhir.core.json template = #bd-national-template ########################## @@ -41,7 +41,7 @@ examples: # other parameters are defined in the ImplementationGuide resource: # https://confluence.hl7.org/display/FHIR/Implementation+Guide+Parameters -# for more documentation on implementation guides and templates, see the FHIR Guidance ImplementationGuide +# for more documentation on implementation guides and templates, see the FHIR Guidance ImplementationGuide # http://build.fhir.org/ig/FHIR/ig-guidance output = /var/www/html diff --git a/input/fsh/ovserbation-profile.fsh b/input/fsh/ovserbation-profile.fsh new file mode 100644 index 0000000..53d5f7e --- /dev/null +++ b/input/fsh/ovserbation-profile.fsh @@ -0,0 +1,84 @@ +Profile: BDObservationProfile +Id: bd-observation +Parent: Observation +Title: "Bangladesh Observation Profile" +Description: "Bangladesh Observation Profile" +* ^url = "https://fhir.dghs.gov.bd/core/StructureDefinition/bd-observation" +* ^version = "1.0.0" +* ^status = #active + +* identifier 1..* MS +* identifier.value 1..1 + +* status 1..1 + +* partOf 0..* + +* category 1..* MS +* category from http://hl7.org/fhir/ValueSet/observation-category (required) +* category ^definition = "Type of category" +* category ^comment = "E.g. vital, physical examination" + +* code 1..1 MS +* code from http://hl7.org/fhir/ValueSet/observation-codes (required) +* code ^definition = "Type of test/measurement" +* code ^comment = "E.g., Hb, RBS, CBC" + +// Subject: Reference to Patient Profile (Required) +* subject 1..1 MS +* subject.reference 1..1 MS +* subject.display 1..1 MS +* subject.identifier 0..1 +* subject ^definition = "Reference to Patient Profile" +* subject ^comment = "EX: http://mci.mcishr.dghs.gov.bd/api/v1/patients/98002412586" + + +* encounter 1..1 MS +* encounter.reference 1..1 MS +* encounter ^definition = "Reference to Patient Profile" +* encounter ^comment = "EX: uuid:34c38499-58ab-41e0-8e94-c3931491ad0e - bundle encounter uuid from local" + +* performer 1..* +* performer.reference 1..1 + +// Value[x]: Result value (Optional, but constrained) +* value[x] 0..1 +* value[x] ^definition = "Result value (Quantity, string, code, boolean, etc.)" +* valueQuantity 0..1 +* valueQuantity ^comment = "If numeric, must include UCUM unit" +* valueQuantity.system 0..1 +* valueQuantity.code 0..1 +* valueString 0..1 +* valueCodeableConcept 0..1 +* valueBoolean 0..1 +* valueInteger 0..1 +* valueRange 0..1 +* valueRatio 0..1 +* valueSampledData 0..1 +* valueTime 0..1 +* valueDateTime 0..1 +* valuePeriod 0..1 + +* interpretation 0..* +* interpretation from http://hl7.org/fhir/ValueSet/observation-interpretation (required) +* interpretation ^definition = "Type of test/measurement" +* interpretation ^comment = "E.g.: High, low, normal, etc" + +* method 0..1 +* method from http://hl7.org/fhir/ValueSet/observation-methods (required) +* method ^definition = "Type of observation method" +* method ^comment = "E.g.: Technique, Total measurement" + +// Issued: Date/time result was issued (Optional) +* issued 0..1 +* issued ^definition = "Date/time result was issued" + +// Reference Range: Normal reference range (Optional) +* referenceRange 0..* +* referenceRange ^definition = "Normal reference range" +* referenceRange ^comment = "Optional" + +// Specimen: Specimen used for the observation (Optional) +* specimen 0..1 +* specimen ^definition = "Specimen used for the observation" +* specimen ^comment = "Optional" \ No newline at end of file diff --git a/input/fsh/profile/BDLocation.fsh b/input/fsh/profile/BDLocation.fsh new file mode 100644 index 0000000..4aab6ef --- /dev/null +++ b/input/fsh/profile/BDLocation.fsh @@ -0,0 +1,8 @@ +Profile: BDLocation +Id: bd-location +Parent: Location +Title: "Location of Immunization for Bangladesh" +Description: "Address for Bangladesh Standard" + +* address 1..1 +* address only BDAddress diff --git a/input/fsh/profile/BDMedicationRequest.fsh b/input/fsh/profile/BDMedicationRequest.fsh index 1b71220..e065fb5 100644 --- a/input/fsh/profile/BDMedicationRequest.fsh +++ b/input/fsh/profile/BDMedicationRequest.fsh @@ -7,8 +7,6 @@ Parent: MedicationRequest Title: "Medication Request Profile for Bangladesh-V2" Description: "Profile of MedicationRequest Bangladesh Standard V2" - - * identifier 1..* * medication[x] 1..1 MS * medication[x] only Reference(BDMedication) or CodeableConcept @@ -16,8 +14,10 @@ Description: "Profile of MedicationRequest Bangladesh Standard V2" // TODO: BDEncounter referencing * authoredOn 1..1 -* requester 1..1 * reported[x] 1..1 +* requester 1..1 -* requester only Reference(BDPractitioner) -* reported[x] only Reference(BDOrganization) +* reported[x] only Reference(BDOrganization) or boolean +* requester only Reference(BDPractitioner or BDOrganization) +* priorPrescription only Reference(BDMedicationRequest) +* basedOn only Reference(BDMedicationRequest or ServiceRequest or CarePlan or ImmunizationRecommendation) \ No newline at end of file diff --git a/input/fsh/profile/immunization-profile.fsh b/input/fsh/profile/immunization-profile.fsh index f08e60f..a57e9a3 100644 --- a/input/fsh/profile/immunization-profile.fsh +++ b/input/fsh/profile/immunization-profile.fsh @@ -1,7 +1,7 @@ // @Name: Profile -// @Description: Immunization Profile of the Bangladeshi Patient. +// @Description: Immunization Profile of the Bangladeshi Patient. Profile: BDImmunizationProfile Id: bd-immunization Parent: Immunization @@ -28,7 +28,7 @@ Description: "Bangladesh Immunization Profile" * manufacturer ^definition = "Vaccine manufacturer" * ^url = "https://fhir.dghs.gov.bd/core/StructureDefinition/bd-organization" -* lotNumber 0..1 +* lotNumber 0..1 * lotNumber ^short = "Vaccine Lot Number" * lotNumber ^definition = "Vaccine lot or batch number" @@ -36,19 +36,19 @@ Description: "Bangladesh Immunization Profile" * expirationDate ^short = "Expiration Date" * expirationDate ^definition = "Expiration date of vaccine lot" -// * patient 1..1 -// * patient ^definition = "The patient receiving the vaccine" -// * patient from BDPatientProfile +* patient 1..1 +* patient ^definition = "The patient receiving the vaccine" +* patient only Reference(BDPatientProfile) * encounter 1..1 * encounter ^definition = "Encounter during which vaccine was administered" -//* encounter from BD Encounter Profile +* encounter only Reference(BDEncounterProfile) -* occurrence[x] 1..1 +* occurrence[x] 1..1 -// * location 0..1 -// * location ^definition = "Location where vaccine was administered" -// * location only BDAddress +* location 0..1 +* location ^definition = "Location where vaccine was administered" +* location only Reference(BDLocation) * site 0..1 * site ^definition = "Body site of administration" @@ -60,14 +60,15 @@ Description: "Bangladesh Immunization Profile" * doseQuantity 0..1 * doseQuantity ^definition = "Amount of vaccine administered" -//* doseQuantity from UCUM Units -* ^url = "http://unitsofmeasure.org" +* doseQuantity.system = "http://unitsofmeasure.org" -* performer 0..* +* performer 0..* * performer ^definition = "Individual who performed the immunization" -//* performer from BD Practitioner Profile -* ^url = "https://fhir.dghs.gov.bd/core/StructureDefinition/bd-practitioner" +* performer.actor only Reference(BDPractitioner) * reaction 0..* * reaction ^definition = "Adverse reaction following immunization" -* reaction.detail only Reference(Observation) \ No newline at end of file +//TODO: change to BDObservation after creating the profile +* reaction.detail only Reference(Observation) +//TODO change to BDObservation after creating the profile +* reasonReference only Reference(Condition or Observation or DiagnosticReport) \ No newline at end of file diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..6c4c30d --- /dev/null +++ b/nginx.conf @@ -0,0 +1,27 @@ +pid /var/cache/nginx/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Logging + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + error_log /var/log/nginx/error.log warn; + + server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + } +} \ No newline at end of file diff --git a/sushi-config.yaml b/sushi-config.yaml index 6df7cf0..d3645b4 100644 --- a/sushi-config.yaml +++ b/sushi-config.yaml @@ -10,7 +10,7 @@ version: 0.2.0 fhirVersion: 4.0.1 copyrightYear: 2025+ releaseLabel: CI Build -FSHOnly: true +FSHOnly: false publisher: name: Directorate General of Health Services (DGHS), Bangladesh url: https://dghs.gov.bd