This commit is contained in:
mario
2025-03-07 13:47:44 +07:00
commit c4efec5a14
3358 changed files with 303774 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
logs/*
volumes/*
config/letsencrypt/*
config/certbot/*
!config/letsencrypt/.gitkeep
!config/certbot/.gitkeep

View File

@@ -0,0 +1,7 @@
#!/bin/sh
# Start oauth2-proxy
oauth2-proxy --config=/etc/oauth2-proxy/oauth2-proxy.cfg &
# Start nginx
nginx -g "daemon off;"

View File

@@ -0,0 +1,240 @@
worker_processes auto;
error_log /var/logs/nginx/error.log debug;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
use epoll;
multi_accept on;
}
http {
include '/etc/nginx/mime.types';
default_type application/octet-stream;
keepalive_timeout 65;
keepalive_requests 100000;
tcp_nopush on;
tcp_nodelay on;
proxy_buffers 16 16k;
proxy_buffer_size 32k;
proxy_busy_buffers_size 64k;
proxy_max_temp_file_size 128k;
gzip on;
gzip_disable "msie6";
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;
server {
listen 80;
server_name YOUR_DOMAIN;
client_max_body_size 0;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name YOUR_DOMAIN;
ssl_certificate /etc/letsencrypt/live/YOUR_DOMAIN/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/YOUR_DOMAIN/privkey.pem;
root /var/www/html;
gzip on;
gzip_types text/css application/javascript application/json image/svg+xml;
gzip_comp_level 9;
etag on;
location /sw.js {
add_header Cache-Control "no-cache";
proxy_cache_bypass $http_pragma;
proxy_cache_revalidate on;
expires off;
access_log off;
}
location /oauth2 {
expires -1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Auth-Request-Redirect $request_uri;
proxy_pass http://localhost:4180$uri$is_args$args;
}
location /oauth2/callback {
proxy_pass http://localhost:4180;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /oauth2/sign_out {
expires -1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Auth-Request-Redirect /oauth2/sign_in;
proxy_pass http://localhost:4180;
}
location /pacs/ {
auth_request /oauth2/auth;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
expires 0;
add_header Cache-Control private;
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Origin, X-Requested-With, Content-Type, Accept' always;
if ($request_method = OPTIONS) {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Authorization, Origin, X-Requested-With, Content-Type, Accept';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
rewrite ^/pacs/(.*) /dcm4chee-arc/aets/DCM4CHEE/rs/$1 break;
proxy_pass http://arc:8080;
}
location /pacs-admin {
return 301 /pacs-admin/;
}
# Redirect /pacs-admin to /dcm4chee-arc/ui2/
location = /pacs-admin {
return 301 $scheme://$host/dcm4chee-arc/ui2/;
}
# Handle /pacs-admin/ requests
location /pacs-admin/ {
return 301 $scheme://$host/dcm4chee-arc/ui2/;
}
# Proxy pass for /dcm4chee-arc/ui2/
location /dcm4chee-arc/ui2/ {
error_page 401 = /oauth2/sign_in?rd=$scheme://$host$request_uri;
auth_request /oauth2/auth?allowed_groups=pacsadmin;
auth_request_set $user $upstream_http_x_auth_request_user;
auth_request_set $token $upstream_http_x_auth_request_access_token;
auth_request_set $auth_cookie $upstream_http_set_cookie;
proxy_set_header X-User $user;
proxy_set_header X-Access-Token $token;
add_header Set-Cookie $auth_cookie;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
expires 0;
add_header Cache-Control private;
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Origin, X-Requested-With, Content-Type, Accept' always;
if ($request_method = OPTIONS) {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Authorization, Origin, X-Requested-With, Content-Type, Accept';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
proxy_pass http://arc:8080;
}
# Proxy pass for other /dcm4chee-arc/ requests
location /dcm4chee-arc/ {
proxy_pass http://arc:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /pacs {
return 301 /pacs/;
}
location /ohif-viewer/ {
expires -1;
error_page 401 = /oauth2/sign_in?rd=$scheme://$host$request_uri;
auth_request /oauth2/auth;
auth_request_set $user $upstream_http_x_auth_request_user;
auth_request_set $token $upstream_http_x_auth_request_access_token;
auth_request_set $auth_cookie $upstream_http_set_cookie;
proxy_set_header X-User $user;
proxy_set_header X-Access-Token $token;
add_header Set-Cookie $auth_cookie;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-Proto $scheme;
index index.html;
try_files $uri $uri/ /index.html;
}
location /ohif-viewer {
return 301 /ohif-viewer/;
}
location = / {
return 301 /ohif-viewer/;
}
location / {
add_header Cache-Control "no-store, no-cache, must-revalidate";
add_header 'Cross-Origin-Opener-Policy' 'same-origin' always;
add_header 'Cross-Origin-Embedder-Policy' 'require-corp' always;
}
location /keycloak/ {
proxy_pass http://keycloak:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /keycloak {
return 301 /keycloak/;
}
}
}

View File

@@ -0,0 +1,22 @@
http_address="0.0.0.0:4180"
cookie_secret="GENERATEACOOKIESECRET----------------------="
email_domains=["*"]
cookie_secure="false"
cookie_expire="9m30s"
cookie_refresh="5m"
client_secret="2Xtlde7aozdkzzYHdIxQNfPDr0wNPTgg"
client_id="ohif_viewer"
redirect_url="http://YOUR_DOMAIN/oauth2/callback"
ssl_insecure_skip_verify = true
insecure_oidc_allow_unverified_email = true
pass_access_token = true
provider="keycloak-oidc"
provider_display_name="Keycloak"
user_id_claim="oid"
oidc_email_claim="sub"
scope="openid"
pass_host_header=true
code_challenge_method="S256"
oidc_issuer_url="http://YOUR_DOMAIN/keycloak/realms/ohif"
insecure_oidc_skip_issuer_verification = true

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,4 @@
STORAGE_DIR=/storage/fs1
POSTGRES_DB=pacsdb
POSTGRES_USER=pacs
POSTGRES_PASSWORD=pacs

View File

@@ -0,0 +1,161 @@
version: '3.8'
services:
ldap:
image: dcm4che/slapd-dcm4chee:2.6.3-29.0
logging:
driver: json-file
options:
max-size: "10m"
ports:
- "389:389"
env_file: docker-compose.env
volumes:
- ~/dcm4chee-arc/ldap:/var/lib/ldap
- ~/dcm4chee-arc/slapd.d:/etc/ldap/slapd.d
db:
image: dcm4che/postgres-dcm4chee:14.5-29
logging:
driver: json-file
options:
max-size: "10m"
ports:
- "5432:5432"
env_file: docker-compose.env
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
- ~/dcm4chee-arc/db:/var/lib/postgresql/data
arc:
image: dcm4che/dcm4chee-arc-psql:5.29.0
logging:
driver: json-file
options:
max-size: "10m"
ports:
- "8080:8080"
- "8443:8443"
- "9990:9990"
- "9993:9993"
- "11112:11112"
- "2762:2762"
- "2575:2575"
- "12575:12575"
env_file: docker-compose.env
environment:
WILDFLY_CHOWN: /opt/wildfly/standalone /storage
WILDFLY_WAIT_FOR: ldap:389 db:5432
depends_on:
- ldap
- db
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
- ~/dcm4chee-arc/wildfly:/opt/wildfly/standalone
- ~/dcm4chee-arc/storage:/storage
ohif_viewer:
build:
context: ./../../../../
dockerfile: ./platform/app/.recipes/Nginx-Dcm4chee-Keycloak/dockerfile
image: webapp:latest
container_name: webapp
ports:
- '443:443' # SSL
- '80:80' # Web
depends_on:
keycloak:
condition: service_healthy
restart: on-failure
networks:
- default
extra_hosts:
- 'host.docker.internal:host-gateway'
environment:
- OAUTH2_PROXY_SKIP_PROVIDER_BUTTON=true
volumes:
- ./config/nginx.conf:/etc/nginx/nginx.conf
- ./config/oauth2-proxy.cfg:/etc/oauth2-proxy/oauth2-proxy.cfg
- ./config/letsencrypt:/etc/letsencrypt
- ./config/certbot:/var/www/certbot
keycloak:
image: quay.io/keycloak/keycloak:24.0.5
command: 'start-dev --import-realm'
hostname: keycloak
container_name: keycloak
volumes:
- ./config/ohif-keycloak-realm.json:/opt/keycloak/data/import/ohif-keycloak-realm.json
environment:
KC_DB_URL_HOST: postgres
KC_DB: postgres
KC_DB_URL: 'jdbc:postgresql://postgres:5432/keycloak'
KC_DB_SCHEMA: public
KC_DB_USERNAME: keycloak
KC_DB_PASSWORD: password
KC_HOSTNAME_ADMIN_URL: http://YOUR_DOMAIN/keycloak/
KC_HOSTNAME_URL: http://YOUR_DOMAIN/keycloak/
KC_HOSTNAME_STRICT_BACKCHANNEL: true
KC_HOSTNAME_STRICT_HTTPS: false
KC_HTTP_ENABLED: true
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
KC_HEALTH_ENABLED: true
KC_METRICS_ENABLED: true
KC_PROXY: edge
KC_PROXY_HEADERS: xforwarded
KEYCLOAK_JDBC_PARAMS: connectTimeout=40000
KC_LOG_LEVEL: INFO
KC_HOSTNAME_DEBUG: true
PROXY_ADDRESS_FORWARDING: true
ports:
- 8081:8080
depends_on:
- postgres
restart: unless-stopped
networks:
- default
extra_hosts:
- 'host.docker.internal:host-gateway'
healthcheck:
test:
[
"CMD-SHELL",
"exec 3<>/dev/tcp/YOUR_DOMAIN/8080;echo -e \"GET /health/ready HTTP/1.1\r\nhost: http://localhost\r\nConnection: close\r\n\r\n\" >&3;grep \"HTTP/1.1 200 OK\" <&3"
]
interval: 1s
timeout: 5s
retries: 10
start_period: 60s
postgres:
image: postgres:15
hostname: postgres
container_name: postgres
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_DB: keycloak
POSTGRES_USER: keycloak
POSTGRES_PASSWORD: password
restart: unless-stopped
networks:
- default
certbot:
image: certbot/certbot
container_name: certbot
volumes:
- ./config/letsencrypt:/etc/letsencrypt
- ./config/certbot:/var/www/certbot
entrypoint: /bin/sh -c "trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;"
volumes:
postgres_data:
driver: local
networks:
default:
driver: bridge

View File

@@ -0,0 +1,50 @@
# Stage 1: Build the application
FROM node:18.16.1-slim as builder
# Setup the working directory
RUN mkdir /usr/src/app
WORKDIR /usr/src/app
# Install dependencies
RUN apt-get update && apt-get install -y build-essential python3
# Copy the entire project
COPY ./ /usr/src/app/
# Install node dependencies
RUN yarn config set workspaces-experimental true
RUN yarn install
# Set the environment for the build
ENV APP_CONFIG=config/docker-nginx-dcm4chee-keycloak.js
# Build the application
RUN yarn run build
# Stage 2: Setup the NGINX environment with OAuth2 Proxy
FROM nginx:alpine
# Install dependencies for oauth2-proxy
RUN apk add --no-cache curl
# Create necessary directories
RUN mkdir -p /var/logs/nginx /var/www/html /etc/oauth2-proxy
# Download and install oauth2-proxy
RUN curl -L https://github.com/oauth2-proxy/oauth2-proxy/releases/download/v7.4.0/oauth2-proxy-v7.4.0.linux-amd64.tar.gz -o oauth2-proxy.tar.gz && \
tar -xvzf oauth2-proxy.tar.gz && \
mv oauth2-proxy-v7.4.0.linux-amd64/oauth2-proxy /usr/local/bin/ && \
rm -rf oauth2-proxy-v7.4.0.linux-amd64 oauth2-proxy.tar.gz
# Copy the built application
COPY --from=builder /usr/src/app/platform/app/dist /var/www/html
# Copy the entrypoint script
COPY ./platform/app/.recipes/Nginx-Dcm4chee-Keycloak/config/entrypoint.sh /entrypoint.sh
# Expose necessary ports
EXPOSE 80 443 4180
# Set the entrypoint script as the entrypoint
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

View File

@@ -0,0 +1 @@
America/New_York

View File

@@ -0,0 +1,86 @@
worker_processes auto;
error_log /var/logs/nginx/error.log debug;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
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/logs/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
gzip on;
gzip_disable "msie6";
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
client_max_body_size 0;
# Handle /pacs requests and rewrite them to the correct dcm4chee-arc UI path
# This allows accessing the dcm4chee-arc UI through the /pacs URL
location /pacs {
rewrite ^/pacs(.*)$ /dcm4chee-arc/ui2$1 break;
proxy_pass http://arc:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_request_buffering off;
expires 0;
add_header Cache-Control private;
}
# Proxy all dcm4chee-arc requests
# This block handles all API requests and general dcm4chee-arc paths
location /dcm4chee-arc/ {
proxy_pass http://arc:8080/dcm4chee-arc/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_request_buffering off;
}
location /sw.js {
add_header Cache-Control "no-cache";
proxy_cache_bypass $http_pragma;
proxy_cache_revalidate on;
expires off;
access_log off;
}
location / {
root /var/www/html;
index index.html;
try_files $uri $uri/ /index.html;
add_header Cache-Control "no-store, no-cache, must-revalidate";
add_header Cross-Origin-Opener-Policy 'same-origin' always;
add_header Cross-Origin-Embedder-Policy 'require-corp' always;
}
}
}

View File

@@ -0,0 +1,4 @@
STORAGE_DIR=/storage/fs1
POSTGRES_DB=pacsdb
POSTGRES_USER=pacs
POSTGRES_PASSWORD=pacs

View File

@@ -0,0 +1,73 @@
services:
ldap:
image: dcm4che/slapd-dcm4chee:2.6.3-29.0
logging:
driver: json-file
options:
max-size: "10m"
ports:
- "389:389"
env_file: docker-compose.env
volumes:
- ~/dcm4chee-arc/ldap:/var/lib/ldap
- ~/dcm4chee-arc/slapd.d:/etc/ldap/slapd.d
db:
image: dcm4che/postgres-dcm4chee:14.5-29
logging:
driver: json-file
options:
max-size: "10m"
ports:
- "5432:5432"
env_file: docker-compose.env
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
- ~/dcm4chee-arc/db:/var/lib/postgresql/data
arc:
image: dcm4che/dcm4chee-arc-psql:5.29.0
logging:
driver: json-file
options:
max-size: "10m"
ports:
- "8080:8080"
- "8443:8443"
- "9990:9990"
- "9993:9993"
- "11112:11112"
- "2762:2762"
- "2575:2575"
- "12575:12575"
env_file: docker-compose.env
environment:
WILDFLY_CHOWN: /opt/wildfly/standalone /storage
WILDFLY_WAIT_FOR: ldap:389 db:5432
depends_on:
- ldap
- db
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
- ~/dcm4chee-arc/wildfly:/opt/wildfly/standalone
- ~/dcm4chee-arc/storage:/storage
ohif_viewer:
build:
# Project root
context: ./../../../../
# Relative to context
dockerfile: ./platform/app/.recipes/Nginx-Dcm4chee/dockerfile
image: webapp:latest
container_name: ohif_dcm4chee
volumes:
# Nginx config
- ./config/nginx.conf:/etc/nginx/nginx.conf
# Logs
- ./logs/nginx:/var/logs/nginx
# Let's Encrypt
# - letsencrypt_certificates:/etc/letsencrypt
# - letsencrypt_challenges:/var/www/letsencrypt
ports:
- '443:443' # SSL
- '80:80' # Web
restart: on-failure

View File

@@ -0,0 +1,43 @@
# Stage 1: Build the application
FROM node:18.16.1-slim as builder
# Setup the working directory
RUN mkdir /usr/src/app
WORKDIR /usr/src/app
# Install dependencies
# apt-get update is combined with apt-get install to avoid using outdated packages
RUN apt-get update && apt-get install -y build-essential python3
# Copy package.json and other dependency-related files first
# Assuming your package.json and yarn.lock or similar are located in the project root
COPY ./ /usr/src/app/
# Install node dependencies
RUN yarn config set workspaces-experimental true
RUN yarn install
# Copy the rest of the application code
# set QUICK_BUILD to true to make the build faster for dev
ENV APP_CONFIG=config/docker-nginx-dcm4chee.js
# Build the application
RUN yarn run build
# # Stage 2: Bundle the built application into a Docker container which runs NGINX using Alpine Linux
FROM nginx:alpine
# # Create directories for logs and html content if they don't already exist
RUN mkdir -p /var/log/nginx /var/www/html
# # Copy build output to serve static files
COPY --from=builder /usr/src/app/platform/app/dist /var/www/html
# # Expose HTTP and HTTPS ports
EXPOSE 80 443
# # Start NGINX
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -0,0 +1 @@
America/New_York

View File

@@ -0,0 +1,6 @@
logs/*
volumes/*
config/letsencrypt/*
config/certbot/*
!config/letsencrypt/.gitkeep
!config/certbot/.gitkeep

View File

@@ -0,0 +1,7 @@
#!/bin/sh
# Start oauth2-proxy
oauth2-proxy --config=/etc/oauth2-proxy/oauth2-proxy.cfg &
# Start nginx
nginx -g "daemon off;"

View File

@@ -0,0 +1,210 @@
worker_processes 2;
error_log /var/logs/nginx/mydomain.error.log;
pid /var/run/nginx.pid;
include /usr/share/nginx/modules/*.conf;
events {
worker_connections 1024;
use epoll;
multi_accept on;
}
http {
include '/etc/nginx/mime.types';
default_type application/octet-stream;
keepalive_timeout 65;
keepalive_requests 100000;
tcp_nopush on;
tcp_nodelay on;
proxy_buffers 16 16k;
proxy_buffer_size 32k;
proxy_busy_buffers_size 64k;
proxy_max_temp_file_size 128k;
server {
listen 80;
server_name YOUR_DOMAIN;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name YOUR_DOMAIN;
ssl_certificate /etc/letsencrypt/live/ohifviewer.duckdns.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ohifviewer.duckdns.org/privkey.pem;
root /var/www/html;
gzip on;
gzip_types text/css application/javascript application/json image/svg+xml;
gzip_comp_level 9;
etag on;
location /sw.js {
add_header Cache-Control "no-cache";
proxy_cache_bypass $http_pragma;
proxy_cache_revalidate on;
expires off;
access_log off;
}
location /oauth2 {
expires -1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Auth-Request-Redirect $request_uri;
proxy_pass http://localhost:4180$uri$is_args$args;
}
location /oauth2/callback {
proxy_pass http://localhost:4180;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /oauth2/sign_out {
expires -1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Auth-Request-Redirect /oauth2/sign_in;
proxy_pass http://localhost:4180;
}
location /pacs-admin/ {
error_page 401 = /oauth2/sign_in?rd=$scheme://$host$request_uri;
auth_request /oauth2/auth?allowed_groups=pacsadmin;
auth_request_set $user $upstream_http_x_auth_request_user;
auth_request_set $token $upstream_http_x_auth_request_access_token;
auth_request_set $auth_cookie $upstream_http_set_cookie;
proxy_set_header X-User $user;
proxy_set_header X-Access-Token $token;
add_header Set-Cookie $auth_cookie;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
expires 0;
add_header Cache-Control private;
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Origin, X-Requested-With, Content-Type, Accept' always;
if ($request_method = OPTIONS) {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Authorization, Origin, X-Requested-With, Content-Type, Accept';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
proxy_pass http://orthanc:8042/;
}
location /pacs-admin {
return 301 /pacs-admin/;
}
location /pacs/ {
auth_request /oauth2/auth;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
expires 0;
add_header Cache-Control private;
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Origin, X-Requested-With, Content-Type, Accept' always;
if ($request_method = OPTIONS) {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Authorization, Origin, X-Requested-With, Content-Type, Accept';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
proxy_pass http://orthanc:8042/dicom-web/;
}
location /pacs {
return 301 /pacs/;
}
location /ohif-viewer/ {
expires -1;
error_page 401 = /oauth2/sign_in?rd=$scheme://$host$request_uri;
auth_request /oauth2/auth;
auth_request_set $user $upstream_http_x_auth_request_user;
auth_request_set $token $upstream_http_x_auth_request_access_token;
auth_request_set $auth_cookie $upstream_http_set_cookie;
proxy_set_header X-User $user;
proxy_set_header X-Access-Token $token;
add_header Set-Cookie $auth_cookie;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-Proto $scheme;
index index.html;
try_files $uri $uri/ /index.html;
}
location /ohif-viewer {
return 301 /ohif-viewer/;
}
location = / {
return 301 /ohif-viewer/;
}
location / {
add_header Cache-Control "no-store, no-cache, must-revalidate";
add_header 'Cross-Origin-Opener-Policy' 'same-origin' always;
add_header 'Cross-Origin-Embedder-Policy' 'require-corp' always;
}
location /keycloak/ {
proxy_pass http://keycloak:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /keycloak {
return 301 /keycloak/;
}
}
}

View File

@@ -0,0 +1,22 @@
http_address="0.0.0.0:4180"
cookie_secret="GENERATEACOOKIESECRET----------------------="
email_domains=["*"]
cookie_secure="false"
cookie_expire="9m30s"
cookie_refresh="5m"
client_secret="2Xtlde7aozdkzzYHdIxQNfPDr0wNPTgg"
client_id="ohif_viewer"
redirect_url="http://YOUR_DOMAIN/oauth2/callback"
ssl_insecure_skip_verify = true
insecure_oidc_allow_unverified_email = true
pass_access_token = true
provider="keycloak-oidc"
provider_display_name="Keycloak"
user_id_claim="oid"
oidc_email_claim="sub"
scope="openid"
pass_host_header=true
code_challenge_method="S256"
oidc_issuer_url="http://YOUR_DOMAIN/keycloak/realms/ohif"
insecure_oidc_skip_issuer_verification = true

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,89 @@
{
"Name": "Orthanc inside Docker",
"StorageDirectory": "/var/lib/orthanc/db",
"IndexDirectory": "/var/lib/orthanc/db",
"StorageCompression": false,
"MaximumStorageSize": 0,
"MaximumPatientCount": 0,
"LuaScripts": [],
"Plugins": ["/usr/share/orthanc/plugins", "/usr/local/share/orthanc/plugins"],
"ConcurrentJobs": 2,
"HttpServerEnabled": true,
"HttpPort": 8042,
"HttpDescribeErrors": true,
"HttpCompressionEnabled": true,
"DicomServerEnabled": true,
"DicomAet": "ORTHANC",
"DicomCheckCalledAet": false,
"DicomPort": 4242,
"DefaultEncoding": "Latin1",
"DeflatedTransferSyntaxAccepted": true,
"JpegTransferSyntaxAccepted": true,
"Jpeg2000TransferSyntaxAccepted": true,
"JpegLosslessTransferSyntaxAccepted": true,
"JpipTransferSyntaxAccepted": true,
"Mpeg2TransferSyntaxAccepted": true,
"RleTransferSyntaxAccepted": true,
"UnknownSopClassAccepted": false,
"DicomScpTimeout": 30,
"RemoteAccessAllowed": true,
"SslEnabled": false,
"SslCertificate": "certificate.pem",
"AuthenticationEnabled": false,
"RegisteredUsers": {
"test": "test"
},
"DicomModalities": {},
"DicomModalitiesInDatabase": false,
"DicomAlwaysAllowEcho": true,
"DicomAlwaysAllowStore": true,
"DicomCheckModalityHost": false,
"DicomScuTimeout": 10,
"OrthancPeers": {},
"OrthancPeersInDatabase": false,
"HttpProxy": "",
"HttpVerbose": true,
"HttpTimeout": 10,
"HttpsVerifyPeers": true,
"HttpsCACertificates": "",
"UserMetadata": {},
"UserContentType": {},
"StableAge": 60,
"StrictAetComparison": false,
"StoreMD5ForAttachments": true,
"LimitFindResults": 0,
"LimitFindInstances": 0,
"LimitJobs": 10,
"LogExportedResources": false,
"KeepAlive": true,
"TcpNoDelay": true,
"HttpThreadsCount": 50,
"StoreDicom": true,
"DicomAssociationCloseDelay": 5,
"QueryRetrieveSize": 10,
"CaseSensitivePN": false,
"LoadPrivateDictionary": true,
"Dictionary": {},
"SynchronousCMove": true,
"JobsHistorySize": 10,
"SaveJobs": true,
"OverwriteInstances": false,
"MediaArchiveSize": 1,
"StorageAccessOnFind": "Always",
"MetricsEnabled": true,
"DicomWeb": {
"Enable": true,
"Root": "/dicom-web/",
"EnableWado": true,
"WadoRoot": "/wado",
"Host": "127.0.0.1",
"Ssl": false,
"StowMaxInstances": 10,
"StowMaxSize": 10,
"QidoCaseSensitive": false
}
}

View File

@@ -0,0 +1,126 @@
services:
ohif_viewer:
build:
context: ./../../../../
dockerfile: ./platform/app/.recipes/Nginx-Orthanc-Keycloak/dockerfile
image: webapp:latest
container_name: webapp
ports:
- '443:443' # SSL
- '80:80' # Web
depends_on:
keycloak:
condition: service_healthy
restart: on-failure
networks:
- default
extra_hosts:
- 'host.docker.internal:host-gateway'
environment:
- OAUTH2_PROXY_SKIP_PROVIDER_BUTTON=true
volumes:
# - ../../app/dist /var/www/html
- ./config/nginx.conf:/etc/nginx/nginx.conf
- ./config/oauth2-proxy.cfg:/etc/oauth2-proxy/oauth2-proxy.cfg
- ./config/letsencrypt:/etc/letsencrypt
- ./config/certbot:/var/www/certbot
orthanc:
image: jodogne/orthanc-plugins
hostname: orthanc
container_name: orthanc
volumes:
- ./config/orthanc.json:/etc/orthanc/orthanc.json:ro
- ./volumes/orthanc-db/:/var/lib/orthanc/db/
restart: unless-stopped
networks:
- default
keycloak:
image: quay.io/keycloak/keycloak:24.0.5
command: 'start-dev --import-realm'
hostname: keycloak
container_name: keycloak
volumes:
- ./config/ohif-keycloak-realm.json:/opt/keycloak/data/import/ohif-keycloak-realm.json
environment:
# Database
KC_DB_URL_HOST: postgres
KC_DB: postgres
KC_DB_URL: 'jdbc:postgresql://postgres:5432/keycloak'
KC_DB_SCHEMA: public
KC_DB_USERNAME: keycloak
KC_DB_PASSWORD: password
KC_HOSTNAME_ADMIN_URL: http://YOUR_DOMAIN/keycloak/
KC_HOSTNAME_URL: http://YOUR_DOMAIN/keycloak/
KC_HOSTNAME_STRICT_BACKCHANNEL: true
KC_HOSTNAME_STRICT_HTTPS: false
KC_HTTP_ENABLED: true
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
KC_HEALTH_ENABLED: true
KC_METRICS_ENABLED: true
KC_PROXY: edge
KC_PROXY_HEADERS: xforwarded
KEYCLOAK_JDBC_PARAMS: connectTimeout=40000
KC_LOG_LEVEL: INFO
KC_HOSTNAME_DEBUG: true
# added later
PROXY_ADDRESS_FORWARDING: true
ports:
- 8080:8080
depends_on:
- postgres
restart: unless-stopped
networks:
- default
extra_hosts:
- 'host.docker.internal:host-gateway'
healthcheck:
test: [
'CMD-SHELL',
"exec 3<>/dev/tcp/YOUR_DOMAIN/8080;echo -e \"GET /health/ready HTTP/1.1\r
host: http://localhost\r
Connection: close\r
\r
\" >&3;grep \"HTTP/1.1 200 OK\" <&3",
]
interval: 1s
timeout: 5s
retries: 10
start_period: 60s
postgres:
image: postgres:15
hostname: postgres
container_name: postgres
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_DB: keycloak
POSTGRES_USER: keycloak
POSTGRES_PASSWORD: password
restart: unless-stopped
networks:
- default
certbot:
image: certbot/certbot
container_name: certbot
volumes:
- ./config/letsencrypt:/etc/letsencrypt
- ./config/certbot:/var/www/certbot
entrypoint:
/bin/sh -c "trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;"
volumes:
postgres_data:
driver: local
networks:
default:
driver: bridge

View File

@@ -0,0 +1,57 @@
# Stage 1: Build the application
FROM node:18.16.1-slim as builder
# Setup the working directory
RUN mkdir /usr/src/app
WORKDIR /usr/src/app
# Install dependencies
# apt-get update is combined with apt-get install to avoid using outdated packages
RUN apt-get update && apt-get install -y build-essential python3
# Copy package.json and other dependency-related files first
# Assuming your package.json and yarn.lock or similar are located in the project root
# Todo: this probably can get improved by copying
# only the package json files and running yarn install before
# copying the rest of the files but having a monorepo setup
# makes this a bit more complicated, i wasn't able to get it working
COPY ./ /usr/src/app/
# Install node dependencies
RUN yarn config set workspaces-experimental true
RUN yarn install
# Copy the rest of the application code
# set QUICK_BUILD to true to make the build faster for dev
ENV APP_CONFIG=config/docker-nginx-orthanc-keycloak.js
# Build the application
RUN yarn run build
# Use nginx as the base image
FROM nginx:alpine
# Install dependencies for oauth2-proxy
RUN apk add --no-cache curl
# Create necessary directories
RUN mkdir -p /var/logs/nginx /var/www/html /etc/oauth2-proxy
# Download and install oauth2-proxy
RUN curl -L https://github.com/oauth2-proxy/oauth2-proxy/releases/download/v7.4.0/oauth2-proxy-v7.4.0.linux-amd64.tar.gz -o oauth2-proxy.tar.gz && \
tar -xvzf oauth2-proxy.tar.gz && \
mv oauth2-proxy-v7.4.0.linux-amd64/oauth2-proxy /usr/local/bin/ && \
rm -rf oauth2-proxy-v7.4.0.linux-amd64 oauth2-proxy.tar.gz
COPY --from=builder /usr/src/app/platform/app/dist /var/www/html
# Copy the entrypoint script
COPY ./platform/app/.recipes/Nginx-Orthanc-Keycloak/config/entrypoint.sh /entrypoint.sh
# Expose necessary ports
EXPOSE 80 443 4180
# Set the entrypoint script as the entrypoint
RUN chmod +x entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

View File

@@ -0,0 +1,2 @@
logs/*
volumes/*

View File

@@ -0,0 +1,26 @@
# Docker compose files
# Build
Using docker compose you can build the image with the following command:
```bash
docker-compose build
```
# Run
To run the container use the following command:
```bash
docker-compose up
```
# Routes
http://localhost/ -> OHIF
localhost/pacs -> Orthanc
See [here](../../../docs/docs/deployment/nginx--image-archive.md) for more information about this recipe.

View File

@@ -0,0 +1,86 @@
worker_processes 2;
error_log /var/logs/nginx/mydomain.error.log;
pid /var/run/nginx.pid;
include /usr/share/nginx/modules/*.conf; # See /usr/share/doc/nginx/README.dynamic.
events {
worker_connections 1024; ## Default: 1024
use epoll; # http://nginx.org/en/docs/events.html
multi_accept on; # http://nginx.org/en/docs/ngx_core_module.html#multi_accept
}
# Core Modules Docs:
# http://nginx.org/en/docs/http/ngx_http_core_module.html
http {
include '/etc/nginx/mime.types';
default_type application/octet-stream;
keepalive_timeout 65;
keepalive_requests 100000;
tcp_nopush on;
tcp_nodelay on;
# Nginx `listener` block
server {
listen [::]:80 default_server;
listen 80;
gzip on;
gzip_types text/css application/javascript application/json image/svg+xml;
gzip_comp_level 9;
etag on;
# Reverse Proxy for `orthanc` APIs (including DICOMWeb)
#
location /pacs/ {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
expires 0;
add_header Cache-Control private;
# Add CORS headers
# Note: uncomment the following line to allow all domains to access the Orthanc APIs
# You should actually only allow the domains you trust to access the APIs
# add_header 'Access-Control-Allow-Origin' '*' always;
proxy_pass http://orthanc:8042/;
# By default, this endpoint is protected by CORS (cross-origin-resource-sharing)
# You can add headers to allow other domains to request this resource.
# See the "Updating CORS Settings" example below
}
# Do not cache sw.js, required for offline-first updates.
location /sw.js {
add_header Cache-Control "no-cache";
proxy_cache_bypass $http_pragma;
proxy_cache_revalidate on;
expires off;
access_log off;
}
# Single Page App
# Try files, fallback to index.html
#
location / {
root /var/www/html;
index index.html;
try_files $uri $uri/ /index.html;
add_header Cache-Control "no-store, no-cache, must-revalidate";
add_header 'Cross-Origin-Opener-Policy' 'same-origin' always;
add_header 'Cross-Origin-Embedder-Policy' 'require-corp' always;
}
}
}

View File

@@ -0,0 +1,89 @@
{
"Name": "Orthanc inside Docker",
"StorageDirectory": "/var/lib/orthanc/db",
"IndexDirectory": "/var/lib/orthanc/db",
"StorageCompression": false,
"MaximumStorageSize": 0,
"MaximumPatientCount": 0,
"LuaScripts": [],
"Plugins": ["/usr/share/orthanc/plugins", "/usr/local/share/orthanc/plugins"],
"ConcurrentJobs": 2,
"HttpServerEnabled": true,
"HttpPort": 8042,
"HttpDescribeErrors": true,
"HttpCompressionEnabled": true,
"DicomServerEnabled": true,
"DicomAet": "ORTHANC",
"DicomCheckCalledAet": false,
"DicomPort": 4242,
"DefaultEncoding": "Latin1",
"DeflatedTransferSyntaxAccepted": true,
"JpegTransferSyntaxAccepted": true,
"Jpeg2000TransferSyntaxAccepted": true,
"JpegLosslessTransferSyntaxAccepted": true,
"JpipTransferSyntaxAccepted": true,
"Mpeg2TransferSyntaxAccepted": true,
"RleTransferSyntaxAccepted": true,
"UnknownSopClassAccepted": false,
"DicomScpTimeout": 30,
"RemoteAccessAllowed": true,
"SslEnabled": false,
"SslCertificate": "certificate.pem",
"AuthenticationEnabled": false,
"RegisteredUsers": {
"test": "test"
},
"DicomModalities": {},
"DicomModalitiesInDatabase": false,
"DicomAlwaysAllowEcho": true,
"DicomAlwaysAllowStore": true,
"DicomCheckModalityHost": false,
"DicomScuTimeout": 10,
"OrthancPeers": {},
"OrthancPeersInDatabase": false,
"HttpProxy": "",
"HttpVerbose": true,
"HttpTimeout": 10,
"HttpsVerifyPeers": true,
"HttpsCACertificates": "",
"UserMetadata": {},
"UserContentType": {},
"StableAge": 60,
"StrictAetComparison": false,
"StoreMD5ForAttachments": true,
"LimitFindResults": 0,
"LimitFindInstances": 0,
"LimitJobs": 10,
"LogExportedResources": false,
"KeepAlive": true,
"TcpNoDelay": true,
"HttpThreadsCount": 50,
"StoreDicom": true,
"DicomAssociationCloseDelay": 5,
"QueryRetrieveSize": 10,
"CaseSensitivePN": false,
"LoadPrivateDictionary": true,
"Dictionary": {},
"SynchronousCMove": true,
"JobsHistorySize": 10,
"SaveJobs": true,
"OverwriteInstances": false,
"MediaArchiveSize": 1,
"StorageAccessOnFind": "Always",
"MetricsEnabled": true,
"DicomWeb": {
"Enable": true,
"Root": "/dicom-web/",
"EnableWado": true,
"WadoRoot": "/wado",
"Host": "127.0.0.1",
"Ssl": false,
"StowMaxInstances": 10,
"StowMaxSize": 10,
"QidoCaseSensitive": false
}
}

View File

@@ -0,0 +1,46 @@
# Reference:
# - https://docs.docker.com/compose/compose-file
# - https://eclipsesource.com/blogs/2018/01/11/authenticating-reverse-proxy-with-keycloak/
services:
# Exposed server that's handling incoming web requests
ohif_viewer:
build:
# Project root
context: ./../../../../
# Relative to context
dockerfile: ./platform/app/.recipes/Nginx-Orthanc/dockerfile
image: webapp:latest
container_name: ohif_orthanc
volumes:
# Nginx config
- ./config/nginx.conf:/etc/nginx/nginx.conf
# Logs
- ./logs/nginx:/var/logs/nginx
# Let's Encrypt
# - letsencrypt_certificates:/etc/letsencrypt
# - letsencrypt_challenges:/var/www/letsencrypt
ports:
- '443:443' # SSL
- '80:80' # Web
depends_on:
# - keycloak
- orthanc
restart: on-failure
# LINK: https://hub.docker.com/r/jodogne/orthanc-plugins/
# TODO: Update to use Postgres
# https://github.com/mrts/docker-postgresql-multiple-databases
orthanc:
image: jodogne/orthanc-plugins
hostname: orthanc
container_name: orthancPACS
volumes:
# Config
- ./config/orthanc.json:/etc/orthanc/orthanc.json:ro
# Persist data
- ./volumes/orthanc-db/:/var/lib/orthanc/db/
restart: unless-stopped
ports:
- '4242:4242' # Orthanc REST API
- '8042:8042' # Orthanc HTTP

View File

@@ -0,0 +1,43 @@
# Stage 1: Build the application
FROM node:18.16.1-slim as builder
# Setup the working directory
RUN mkdir /usr/src/app
WORKDIR /usr/src/app
# Install dependencies
# apt-get update is combined with apt-get install to avoid using outdated packages
RUN apt-get update && apt-get install -y build-essential python3
# Copy package.json and other dependency-related files first
# Assuming your package.json and yarn.lock or similar are located in the project root
COPY ./ /usr/src/app/
# Install node dependencies
RUN yarn config set workspaces-experimental true
RUN yarn install
# Copy the rest of the application code
# set QUICK_BUILD to true to make the build faster for dev
ENV APP_CONFIG=config/docker-nginx-orthanc.js
# Build the application
RUN yarn run build
# # Stage 2: Bundle the built application into a Docker container which runs NGINX using Alpine Linux
FROM nginx:alpine
# # Create directories for logs and html content if they don't already exist
RUN mkdir -p /var/log/nginx /var/www/html
# # Copy build output to serve static files
COPY --from=builder /usr/src/app/platform/app/dist /var/www/html
# # Expose HTTP and HTTPS ports
EXPOSE 80 443
# # Start NGINX
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -0,0 +1,261 @@
worker_processes 2;
error_log /var/logs/nginx/mydomain.error.log;
pid /var/run/nginx.pid;
include /usr/share/nginx/modules/*.conf; # See /usr/share/doc/nginx/README.dynamic.
events {
worker_connections 1024; ## Default: 1024
use epoll; # http://nginx.org/en/docs/events.html
multi_accept on; # http://nginx.org/en/docs/ngx_core_module.html#multi_accept
}
# Core Modules Docs:
# http://nginx.org/en/docs/http/ngx_http_core_module.html
http {
include '/usr/local/openresty/nginx/conf/mime.types';
default_type application/octet-stream;
keepalive_timeout 65;
keepalive_requests 100000;
tcp_nopush on;
tcp_nodelay on;
# lua_ settings
#
lua_package_path '/usr/local/openresty/lualib/?.lua;;/usr/local/share/lua/5.4/?.lua;;';
lua_shared_dict discovery 1m; # cache for discovery metadata documents
lua_shared_dict jwks 1m; # cache for JWKs
# lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
variables_hash_max_size 2048;
server_names_hash_bucket_size 128;
server_tokens off;
resolver 8.8.8.8 valid=30s ipv6=off;
resolver_timeout 11s;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
# No idea what this is doing
# https://stackoverflow.com/a/5877989/1867984
# upstream upstream_server {
# # server 10.100.4.200:1010 max_fails=3 fail_timeout=30s;
# server 127.0.0.1:
# }
# Nginx `listener` block
server {
listen [::]:80 default_server;
listen 80;
# listen 443 ssl;
access_log /var/logs/nginx/mydomain.access.log;
# Domain to protect
server_name 127.0.0.1 localhost; # mydomain.com;
proxy_intercept_errors off;
# ssl_certificate /etc/letsencrypt/live/mydomain.co.uk/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/mydomain.co.uk/privkey.pem;
gzip on;
gzip_types text/css application/javascript application/json image/svg+xml;
gzip_comp_level 9;
etag on;
# https://github.com/bungle/lua-resty-session/issues/15
set $session_check_ssi off;
lua_code_cache off;
set $session_secret Eeko7aeb6iu5Wohch9Loo1aitha0ahd1;
set $session_storage cookie;
server_tokens off; # Hides server version num
# [PROTECTED] Reverse Proxy for `orthanc` admin
#
location /pacs-admin/ {
access_by_lua_block {
local opts = {
redirect_uri = "http://127.0.0.1/pacs-admin/admin",
discovery = "http://127.0.0.1/auth/realms/ohif/.well-known/openid-configuration",
token_endpoint_auth_method = "client_secret_basic",
client_id = "pacs",
client_secret = "66279641-eba6-47f5-9fdb-70c4ac74d548",
client_jwt_assertion_expires_in = 60 * 60,
ssl_verify = "no",
scope = "openid email profile",
refresh_session_interval = 900,
redirect_uri_scheme = "http",
redirect_after_logout_uri = "/",
session_contents = {id_token=true}
}
-- call authenticate for OpenID Connect user authentication
local res, err = require("resty.openidc").authenticate(opts)
if err or not res then
ngx.print(err)
ngx.status = 200
ngx.say(err and err or "no access_token provided")
ngx.exit(ngx.HTTP_FORBIDDEN)
end
-- Or set cookie?
-- ngx.req.set_header("Authorization", "Bearer " .. res.access_token)
ngx.req.set_header("X-USER", res.id_token.sub)
}
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
expires 0;
add_header Cache-Control private;
proxy_pass http://orthanc:8042/;
}
# [PROTECTED] Reverse Proxy for `orthanc` APIs (including DICOMWeb)
#
location /pacs/ {
access_by_lua_block {
local opts = {
discovery = "http://127.0.0.1/auth/realms/ohif/.well-known/openid-configuration",
}
-- call bearer_jwt_verify for OAuth 2.0 JWT validation
local res, err = require("resty.openidc").bearer_jwt_verify(opts)
if err or not res then
ngx.status = 403
ngx.say(err and err or "no access_token provided")
ngx.exit(ngx.HTTP_FORBIDDEN)
end
}
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# proxy_set_header Upgrade $http_upgrade;
# proxy_set_header Connection "upgrade";
expires 0;
add_header Cache-Control private;
proxy_pass http://orthanc:8042/;
# By default, this endpoint is protected by CORS (cross-origin-resource-sharing)
# You can add headers to allow other domains to request this resource.
# See the "Updating CORS Settings" example below
}
# Keycloak
#
location /auth/ {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_pass http://keycloak:8080/auth/;
}
# Do not cache sw.js, required for offline-first updates.
location /sw.js {
add_header Cache-Control "no-cache";
proxy_cache_bypass $http_pragma;
proxy_cache_revalidate on;
expires off;
access_log off;
}
# Single Page App
# Try files, fallback to index.html
#
location / {
alias /var/www/html/;
index index.html;
try_files $uri $uri/ /index.html;
add_header Cache-Control "no-store, no-cache, must-revalidate";
add_header 'Cross-Origin-Opener-Policy' 'same-origin' always;
add_header 'Cross-Origin-Embedder-Policy' 'require-corp' always;
}
# EXAMPLE: Reverse Proxy, no auth
# [UNPROTECTED] reverse proxy for `orthanc`
#
# location /pacs/ {
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-For $remote_addr;
# proxy_set_header Host $host;
#
# proxy_pass http://orthanc:8042/;
#
# # OR
# # rewrite ^/pacs(.*) /$1 break;
# # proxy_pass http://orthanc:8042;
# }
# EXAMPLE: Modifying headers to allow requests from other domains
# IE. Updating CORS settings
#
# location / {
# if ($request_method = 'OPTIONS') {
# add_header 'Access-Control-Allow-Origin' '*';
# add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
# #
# # Custom headers and headers various browsers *should* be OK with but aren't
# #
# add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
# #
# # Tell client that this pre-flight info is valid for 20 days
# #
# add_header 'Access-Control-Allow-Headers' 'Authorization';
# add_header 'Access-Control-Allow-Credentials' true;
# add_header 'Access-Control-Max-Age' 1728000;
# add_header 'Content-Length' 0;
# return 204;
# }
# if ($request_method = 'POST') {
# add_header 'Access-Control-Allow-Origin' '*';
# add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
# add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
# add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
# }
# if ($request_method = 'GET') {
# add_header 'Access-Control-Allow-Origin' '*';
# add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
# add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
# add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
# add_header 'Access-Control-Allow-Headers' 'Authorization';
# add_header 'Access-Control-Allow-Credentials' true;
# }
#
# # proxy_http_version 1.1;
#
# # proxy_set_header Host $host;
# # proxy_set_header X-Real-IP $remote_addr;
# # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# # proxy_set_header X-Forwarded-Proto $scheme;
#
# proxy_pass http://orthanc:8042;
# }
# EXAMPLE: Redirect server error pages to the static page /40x.html
#
# error_page 404 /404.html;
# location = /40x.html {
# }
# EXAMPLE: Redirect server error pages to the static page /50x.html
#
# error_page 500 502 503 504 /50x.html;
# location = /50x.html {
# }
}
}

View File

@@ -0,0 +1,89 @@
{
"Name": "Orthanc inside Docker",
"StorageDirectory": "/var/lib/orthanc/db",
"IndexDirectory": "/var/lib/orthanc/db",
"StorageCompression": false,
"MaximumStorageSize": 0,
"MaximumPatientCount": 0,
"LuaScripts": [],
"Plugins": ["/usr/share/orthanc/plugins", "/usr/local/share/orthanc/plugins"],
"ConcurrentJobs": 2,
"HttpServerEnabled": true,
"HttpPort": 8042,
"HttpDescribeErrors": true,
"HttpCompressionEnabled": true,
"DicomServerEnabled": true,
"DicomAet": "ORTHANC",
"DicomCheckCalledAet": false,
"DicomPort": 4242,
"DefaultEncoding": "Latin1",
"DeflatedTransferSyntaxAccepted": true,
"JpegTransferSyntaxAccepted": true,
"Jpeg2000TransferSyntaxAccepted": true,
"JpegLosslessTransferSyntaxAccepted": true,
"JpipTransferSyntaxAccepted": true,
"Mpeg2TransferSyntaxAccepted": true,
"RleTransferSyntaxAccepted": true,
"UnknownSopClassAccepted": false,
"DicomScpTimeout": 30,
"RemoteAccessAllowed": true,
"SslEnabled": false,
"SslCertificate": "certificate.pem",
"AuthenticationEnabled": false,
"RegisteredUsers": {
"test": "test"
},
"DicomModalities": {},
"DicomModalitiesInDatabase": false,
"DicomAlwaysAllowEcho": true,
"DicomAlwaysAllowStore": true,
"DicomCheckModalityHost": false,
"DicomScuTimeout": 10,
"OrthancPeers": {},
"OrthancPeersInDatabase": false,
"HttpProxy": "",
"HttpVerbose": true,
"HttpTimeout": 10,
"HttpsVerifyPeers": true,
"HttpsCACertificates": "",
"UserMetadata": {},
"UserContentType": {},
"StableAge": 60,
"StrictAetComparison": false,
"StoreMD5ForAttachments": true,
"LimitFindResults": 0,
"LimitFindInstances": 0,
"LimitJobs": 10,
"LogExportedResources": false,
"KeepAlive": true,
"TcpNoDelay": true,
"HttpThreadsCount": 50,
"StoreDicom": true,
"DicomAssociationCloseDelay": 5,
"QueryRetrieveSize": 10,
"CaseSensitivePN": false,
"LoadPrivateDictionary": true,
"Dictionary": {},
"SynchronousCMove": true,
"JobsHistorySize": 10,
"SaveJobs": true,
"OverwriteInstances": false,
"MediaArchiveSize": 1,
"StorageAccessOnFind": "Always",
"MetricsEnabled": true,
"DicomWeb": {
"Enable": true,
"Root": "/dicom-web/",
"EnableWado": true,
"WadoRoot": "/wado",
"Host": "127.0.0.1",
"Ssl": false,
"StowMaxInstances": 10,
"StowMaxSize": 10,
"QidoCaseSensitive": false
}
}

View File

@@ -0,0 +1,95 @@
# Reference:
# - https://docs.docker.com/compose/compose-file
# - https://eclipsesource.com/blogs/2018/01/11/authenticating-reverse-proxy-with-keycloak/
version: '3.5'
services:
# Exposed server that's handling incoming web requests
# Underlying image: openresty/openresty:alpine-fat
ohif_viewer:
build:
# Project root
context: ./../../../../
# Relative to context
dockerfile: ./platform/app/.recipes/OpenResty-Orthanc-Keycloak/dockerfile
image: webapp:latest
container_name: webapp
volumes:
# Nginx config
- ./config/nginx.conf:/usr/local/openresty/nginx/conf/nginx.conf:ro
# Logs
- ./logs/nginx:/var/logs/nginx
# Let's Encrypt
# - letsencrypt_certificates:/etc/letsencrypt
# - letsencrypt_challenges:/var/www/letsencrypt
ports:
- '443:443' # SSL
- '80:80' # Web
depends_on:
- keycloak
- orthanc
restart: on-failure
# LINK: https://hub.docker.com/r/jodogne/orthanc-plugins/
# TODO: Update to use Postgres
# https://github.com/mrts/docker-postgresql-multiple-databases
orthanc:
image: jodogne/orthanc-plugins
hostname: orthanc
container_name: orthanc
volumes:
# Config
- ./config/orthanc.json:/etc/orthanc/orthanc.json:ro
# Persist data
- ./volumes/orthanc-db/:/var/lib/orthanc/db/
restart: unless-stopped
# LINK: https://hub.docker.com/r/jboss/keycloak
keycloak:
image: quay.io/keycloak/keycloak:6.0.0
hostname: keycloak
container_name: keycloak
volumes:
# Theme: https://www.keycloak.org/docs/latest/server_development/index.html#_themes
- ./volumes/keycloak-themes/ohif:/opt/jboss/keycloak/themes/ohif
# Previous Realm Config
- ./config/ohif-keycloak-realm.json:/tmp/ohif-keycloak-realm.json
environment:
# Database
DB_VENDOR: postgres
DB_ADDR: postgres
DB_PORT: 5432
DB_DATABASE: keycloak
DB_SCHEMA: public
DB_USER: keycloak
DB_PASSWORD: password
# Keycloak
KEYCLOAK_USER: admin
KEYCLOAK_PASSWORD: password
KEYCLOAK_IMPORT: /tmp/ohif-keycloak-realm.json
# KEYCLOAK_WELCOME_THEME: <theme-name>
# KEYCLOAK_DEFAULT_THEME: <theme-name>
# KEYCLOAK_HOSTNAME: (recommended in prod)
# KEYCLOAK_LOGLEVEL: DEBUG
PROXY_ADDRESS_FORWARDING: 'true'
depends_on:
- postgres
restart: unless-stopped
# LINK: https://hub.docker.com/_/postgres/
postgres:
image: postgres:11.2
hostname: postgres
container_name: postgres
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_DB: keycloak
POSTGRES_USER: keycloak
POSTGRES_PASSWORD: password
restart: unless-stopped
volumes:
postgres_data:
driver: local

View File

@@ -0,0 +1,85 @@
# docker-compose
# --------------
# This dockerfile is used by the `docker-compose.yml` adjacent file. When
# running `docker compose build`, this dockerfile helps build the "webapp" image.
# All paths are relative to the `context`, which is the project root directory.
#
# docker build
# --------------
# If you would like to use this dockerfile to build and tag an image, make sure
# you set the context to the project's root directory:
# https://docs.docker.com/engine/reference/commandline/build/
#
#
# SUMMARY
# --------------
# This dockerfile has two stages:
#
# 1. Building the React application for production
# 2. Setting up our Nginx (OpenResty*) image w/ step one's output
#
# * OpenResty is functionally identical to Nginx with the addition of Lua out of
# the box.
# Stage 1: Build the application
FROM node:18.16.1-slim as builder
RUN mkdir /usr/src/app
WORKDIR /usr/src/app
RUN apt-get update && apt-get install -y build-essential python3
ENV APP_CONFIG=config/docker_openresty-orthanc-keycloak.js
ENV PATH /usr/src/app/node_modules/.bin:$PATH
# Copy all files from the root of the OHIF source and note
# that the Docker ignore file at the root (i.e. ./dockerignore) will filter
# out files and directories that are not needed.
COPY ./ /usr/src/app/
ADD . /usr/src/app/
RUN yarn config set workspaces-experimental true
RUN yarn install
RUN yarn run build
# Stage 2: Bundle the built application into a Docker container
# which runs openresty (nginx) using Alpine Linux
# LINK: https://hub.docker.com/r/openresty/openresty
FROM openresty/openresty:1.21.4.2-0-bullseye-fat
RUN mkdir /var/log/nginx
RUN apt-get update && \
apt-get install -y openssl libssl-dev git gcc wget unzip make&& \
apt-get clean
RUN apt-get install --assume-yes lua5.4 libzmq3-dev lua5.4-dev
RUN cd /tmp && \
wget http://luarocks.org/releases/luarocks-3.9.2.tar.gz && \
tar zxpf luarocks-3.9.2.tar.gz && \
cd luarocks-3.9.2 && \
./configure && \
make && \
make install
# !!!
RUN luarocks install lua-resty-http
# RUN luarocks install lua-nginx-module
RUN luarocks install lua-cjson
RUN luarocks install lua-resty-string
RUN luarocks install lua-resty-session
RUN luarocks install lua-resty-jwt
RUN luarocks install lua-resty-openidc
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
#
RUN luarocks install lua-resty-http
# !!!
RUN luarocks install lua-resty-auto-ssl
# Copy build output to image
COPY --from=builder /usr/src/app/platform/app/dist /var/www/html
ENTRYPOINT ["/usr/local/openresty/nginx/sbin/nginx", "-g", "daemon off;"]

View File

@@ -0,0 +1,196 @@
body {
background-color: #040507;
background-image: url('../img/background.jpg');
background-size: cover;
background-repeat: no-repeat;
width: 100vw;
height: 100vh;
overflow: hidden;
color: #fff;
font-family: sans-serif;
text-shadow: 0px 0px 10px #000;
}
a {
color: #fff;
}
div#kc-content {
position: absolute;
top: 20%;
left: 50%;
width: 550px;
margin-left: -180px;
}
div#kc-form {
float: left;
width: 350px;
}
div#kc-form label {
display: block;
font-size: 16px;
}
div#info-area {
position: fixed;
bottom: 0;
left: 0;
margin-top: 40px;
background-color: rgba(0, 0, 0, 0.4);
padding: 20px;
width: 100%;
}
div#info-area p {
margin-right: 30px;
display: inline;
text-shadow: none;
}
input[type='text'],
input[type='password'] {
color: #333;
font-size: 18px;
margin-bottom: 20px;
background-color: rgba(256, 256, 256, 0.7);
border: 0px solid rgba(0, 0, 0, 0.2);
box-shadow: inset 0 0 2px 2px rgba(0, 0, 0, 0.15);
padding: 10px;
width: 296px;
}
input[type='text']:hover,
input[type='password']:hover {
background-color: rgba(256, 256, 256, 0.9);
}
input[type='submit'] {
border: none;
background: -webkit-linear-gradient(top, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.1));
background: -moz-linear-gradient(top, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.1));
background: -ms-linear-gradient(top, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.1));
background: -o-linear-gradient(top, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.1));
box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.5);
color: rgba(0, 0, 0, 0.6);
font-size: 14px;
font-weight: bold;
padding: 10px;
margin-top: 20px;
margin-right: 10px;
width: 150px;
}
input[type='submit']:hover {
background-color: rgba(255, 255, 255, 0.8);
}
div#kc-form-options div {
display: inline-block;
margin-right: 20px;
font-size: 12px;
}
div#kc-form-options div label {
font-size: 12px;
}
div#kc-feedback {
box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.5);
position: fixed;
top: 0;
left: 0;
width: 100%;
text-align: center;
}
div#kc-feedback-wrapper {
padding: 1em;
}
div.feedback-success {
background-color: rgba(155, 155, 255, 0.1);
}
div.feedback-warning {
background-color: rgba(255, 175, 0, 0.1);
}
div.feedback-error {
background-color: rgba(255, 0, 0, 0.1);
}
div#kc-header {
display: none;
}
div#kc-registration {
margin-bottom: 20px;
}
div#social-login {
border-left: 1px solid rgba(255, 255, 255, 0.2);
float: right;
width: 150px;
padding: 20px 0 200px 40px;
}
div.social-login span {
display: none;
}
div#kc-social-providers ul {
list-style: none;
margin: 0;
padding: 0;
}
div#kc-social-providers ul li {
margin-bottom: 20px;
}
div#kc-social-providers ul li span {
display: inline;
width: 100px;
}
a.zocial {
border: none;
background: -webkit-linear-gradient(
top,
rgba(255, 255, 255, 0.8),
rgba(255, 255, 255, 0.1)
) !important;
background: -moz-linear-gradient(
top,
rgba(255, 255, 255, 0.8),
rgba(255, 255, 255, 0.1)
) !important;
background: -ms-linear-gradient(
top,
rgba(255, 255, 255, 0.8),
rgba(255, 255, 255, 0.1)
) !important;
background: -o-linear-gradient(
top,
rgba(255, 255, 255, 0.8),
rgba(255, 255, 255, 0.1)
) !important;
box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.5);
color: rgba(0, 0, 0, 0.6);
width: 130px;
text-shadow: none;
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
padding-top: 0.2em;
padding-bottom: 0.2em;
}

View File

@@ -0,0 +1,3 @@
parent=base
import=common/keycloak
styles=lib/zocial/zocial.css css/styles.css

View File

@@ -0,0 +1,2 @@
*
!.gitignore

View File

@@ -0,0 +1,137 @@
worker_processes 2;
error_log /var/logs/nginx/mydomain.error.log;
pid /var/run/nginx.pid;
include /usr/share/nginx/modules/*.conf; # See /usr/share/doc/nginx/README.dynamic.
events {
worker_connections 1024; ## Default: 1024
use epoll; # http://nginx.org/en/docs/events.html
multi_accept on; # http://nginx.org/en/docs/ngx_core_module.html#multi_accept
}
# Core Modules Docs:
# http://nginx.org/en/docs/http/ngx_http_core_module.html
http {
include '/usr/local/openresty/nginx/conf/mime.types';
default_type application/octet-stream;
keepalive_timeout 65;
keepalive_requests 100000;
tcp_nopush on;
tcp_nodelay on;
# lua_ settings
#
lua_package_path '/usr/local/openresty/lualib/?.lua;;';
lua_shared_dict discovery 1m; # cache for discovery metadata documents
lua_shared_dict jwks 1m; # cache for JWKs
# lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
variables_hash_max_size 2048;
server_names_hash_bucket_size 128;
server_tokens off;
resolver 8.8.8.8 valid=30s ipv6=off;
resolver_timeout 11s;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
# Nginx `listener` block
server {
listen [::]:80 default_server;
listen 80;
# listen 443 ssl;
access_log /var/logs/nginx/mydomain.access.log;
# Domain to protect
server_name 127.0.0.1 localhost; # mydomain.com;
proxy_intercept_errors off;
# ssl_certificate /etc/letsencrypt/live/mydomain.co.uk/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/mydomain.co.uk/privkey.pem;
gzip on;
gzip_types text/css application/javascript application/json image/svg+xml;
gzip_comp_level 9;
etag on;
# https://github.com/bungle/lua-resty-session/issues/15
set $session_check_ssi off;
lua_code_cache off;
set $session_secret Eeko7aeb6iu5Wohch9Loo1aitha0ahd1;
set $session_storage cookie;
server_tokens off; # Hides server version num
# Reverse Proxy for `orthanc` admin
#
location /pacs-admin/ {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
expires 0;
add_header Cache-Control private;
proxy_pass http://orthanc:8042/;
}
# Reverse Proxy for `orthanc` APIs (including DICOMWeb)
#
location /pacs/ {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
expires 0;
add_header Cache-Control private;
proxy_pass http://orthanc:8042/;
# By default, this endpoint is protected by CORS (cross-origin-resource-sharing)
# You can add headers to allow other domains to request this resource.
# See the "Updating CORS Settings" example below
}
# Do not cache sw.js, required for offline-first updates.
location /sw.js {
add_header Cache-Control "no-cache";
proxy_cache_bypass $http_pragma;
proxy_cache_revalidate on;
expires off;
access_log off;
}
# Single Page App
# Try files, fallback to index.html
#
location / {
alias /var/www/html/;
index index.html;
try_files $uri $uri/ /index.html;
add_header Cache-Control "no-store, no-cache, must-revalidate";
add_header 'Cross-Origin-Opener-Policy' 'same-origin' always;
add_header 'Cross-Origin-Embedder-Policy' 'require-corp' always;
}
# EXAMPLE: Redirect server error pages to the static page /40x.html
#
# error_page 404 /404.html;
# location = /40x.html {
# }
# EXAMPLE: Redirect server error pages to the static page /50x.html
#
# error_page 500 502 503 504 /50x.html;
# location = /50x.html {
# }
}
}

View File

@@ -0,0 +1,89 @@
{
"Name": "Orthanc inside Docker",
"StorageDirectory": "/var/lib/orthanc/db",
"IndexDirectory": "/var/lib/orthanc/db",
"StorageCompression": false,
"MaximumStorageSize": 0,
"MaximumPatientCount": 0,
"LuaScripts": [],
"Plugins": ["/usr/share/orthanc/plugins", "/usr/local/share/orthanc/plugins"],
"ConcurrentJobs": 2,
"HttpServerEnabled": true,
"HttpPort": 8042,
"HttpDescribeErrors": true,
"HttpCompressionEnabled": true,
"DicomServerEnabled": true,
"DicomAet": "ORTHANC",
"DicomCheckCalledAet": false,
"DicomPort": 4242,
"DefaultEncoding": "Latin1",
"DeflatedTransferSyntaxAccepted": true,
"JpegTransferSyntaxAccepted": true,
"Jpeg2000TransferSyntaxAccepted": true,
"JpegLosslessTransferSyntaxAccepted": true,
"JpipTransferSyntaxAccepted": true,
"Mpeg2TransferSyntaxAccepted": true,
"RleTransferSyntaxAccepted": true,
"UnknownSopClassAccepted": false,
"DicomScpTimeout": 30,
"RemoteAccessAllowed": true,
"SslEnabled": false,
"SslCertificate": "certificate.pem",
"AuthenticationEnabled": false,
"RegisteredUsers": {
"test": "test"
},
"DicomModalities": {},
"DicomModalitiesInDatabase": false,
"DicomAlwaysAllowEcho": true,
"DicomAlwaysAllowStore": true,
"DicomCheckModalityHost": false,
"DicomScuTimeout": 10,
"OrthancPeers": {},
"OrthancPeersInDatabase": false,
"HttpProxy": "",
"HttpVerbose": true,
"HttpTimeout": 10,
"HttpsVerifyPeers": true,
"HttpsCACertificates": "",
"UserMetadata": {},
"UserContentType": {},
"StableAge": 60,
"StrictAetComparison": false,
"StoreMD5ForAttachments": true,
"LimitFindResults": 0,
"LimitFindInstances": 0,
"LimitJobs": 10,
"LogExportedResources": false,
"KeepAlive": true,
"TcpNoDelay": true,
"HttpThreadsCount": 50,
"StoreDicom": true,
"DicomAssociationCloseDelay": 5,
"QueryRetrieveSize": 10,
"CaseSensitivePN": false,
"LoadPrivateDictionary": true,
"Dictionary": {},
"SynchronousCMove": true,
"JobsHistorySize": 10,
"SaveJobs": true,
"OverwriteInstances": false,
"MediaArchiveSize": 1,
"StorageAccessOnFind": "Always",
"MetricsEnabled": true,
"DicomWeb": {
"Enable": true,
"Root": "/dicom-web/",
"EnableWado": true,
"WadoRoot": "/wado",
"Host": "127.0.0.1",
"Ssl": false,
"StowMaxInstances": 10,
"StowMaxSize": 10,
"QidoCaseSensitive": false
}
}

View File

@@ -0,0 +1,47 @@
# Reference:
# - https://docs.docker.com/compose/compose-file
# - https://eclipsesource.com/blogs/2018/01/11/authenticating-reverse-proxy-with-keycloak/
version: '3.5'
services:
# Exposed server that's handling incoming web requests
# Underlying image: openresty/openresty:alpine-fat
ohif_viewer:
build:
# Project root
context: ./../../../../
# Relative to context
dockerfile: ./platform/app/.recipes/OpenResty-Orthanc/dockerfile
image: webapp:latest
container_name: webapp
volumes:
# Nginx config
- ./config/nginx.conf:/usr/local/openresty/nginx/conf/nginx.conf:ro
# Logs
- ./logs/nginx:/var/logs/nginx
# Let's Encrypt
# - letsencrypt_certificates:/etc/letsencrypt
# - letsencrypt_challenges:/var/www/letsencrypt
ports:
- '443:443' # SSL
- '80:80' # Web
depends_on:
- orthanc
restart: on-failure
# LINK: https://hub.docker.com/r/jodogne/orthanc-plugins/
# TODO: Update to use Postgres
# https://github.com/mrts/docker-postgresql-multiple-databases
orthanc:
image: jodogne/orthanc-plugins
hostname: orthanc
container_name: orthanc
volumes:
# Config
- ./config/orthanc.json:/etc/orthanc/orthanc.json:ro
# Persist data
- ./volumes/orthanc-db/:/var/lib/orthanc/db/
restart: unless-stopped
ports:
- '4242:4242' # DIMSE

View File

@@ -0,0 +1,79 @@
# docker-compose
# --------------
# This dockerfile is used by the `docker-compose.yml` adjacent file. When
# running `docker compose build`, this dockerfile helps build the "webapp" image.
# All paths are relative to the `context`, which is the project root directory.
#
# docker build
# --------------
# If you would like to use this dockerfile to build and tag an image, make sure
# you set the context to the project's root directory:
# https://docs.docker.com/engine/reference/commandline/build/
#
#
# SUMMARY
# --------------
# This dockerfile has two stages:
#
# 1. Building the React application for production
# 2. Setting up our Nginx (OpenResty*) image w/ step one's output
#
# * OpenResty is functionally identical to Nginx with the addition of Lua out of
# the box.
# Stage 1: Build the application
FROM node:18.16.1-slim as builder
RUN mkdir /usr/src/app
WORKDIR /usr/src/app
# Copy all files from the root of the OHIF source and note
# that the Docker ignore file at the root (i.e. ./dockerignore) will filter
# out files and directories that are not needed.
COPY ./ /usr/src/app/
# For arm builds since parcel doesn't have prebuilt binaries for arm yet
RUN apt-get update && apt-get install -y build-essential python3
# ADD . /usr/src/app/
RUN yarn config set workspaces-experimental true
RUN yarn install
ENV APP_CONFIG=config/docker_openresty-orthanc.js
ENV PATH /usr/src/app/node_modules/.bin:$PATH
ENV QUICK_BUILD true
RUN yarn run build
# ADD . /usr/src/app/
# RUN yarn install
# RUN yarn run build:web
# Stage 2: Bundle the built application into a Docker container
# which runs openresty (nginx) using Alpine Linux
# LINK: https://hub.docker.com/r/openresty/openresty
FROM openresty/openresty:1.15.8.1rc1-0-alpine-fat
RUN mkdir /var/log/nginx
RUN apk add --no-cache openssl
RUN apk add --no-cache openssl-dev
RUN apk add --no-cache git
RUN apk add --no-cache gcc
# !!!
RUN luarocks install lua-resty-openidc
#
RUN luarocks install lua-resty-jwt
RUN luarocks install lua-resty-session
RUN luarocks install lua-resty-http
# !!!
RUN luarocks install lua-resty-openidc
RUN luarocks install luacrypto
# Copy build output to image
COPY --from=builder /usr/src/app/platform/app/dist /var/www/html
ENTRYPOINT ["/usr/local/openresty/nginx/sbin/nginx", "-g", "daemon off;"]

View File

@@ -0,0 +1,2 @@
*
!.gitignore