diff --git a/.gitignore b/.gitignore index 8b2492ed..a7c2bbb8 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,11 @@ !.vscode/extensions.json *.code-workspace MedShakeEHR-base.zip -secrets.yml \ No newline at end of file +secrets.yml +composer.lock +vendor/ +config/config.yml +public_html/thirdparty +public_html/MEDSHAKEEHRPATH +tools/docker/.env +tools/docker/orthanc.json \ No newline at end of file diff --git a/tools/docker/Dockerfile b/tools/docker/Dockerfile new file mode 100644 index 00000000..e2f37238 --- /dev/null +++ b/tools/docker/Dockerfile @@ -0,0 +1,98 @@ +# https://hub.docker.com/_/php +FROM php:8.2-apache +ENV PHPSTAGE=production +ARG DEBIAN_FRONTEND=noninteractive +RUN set -ex; \ + \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + cron \ + ghostscript \ + git \ + imagemagick \ + mariadb-client \ + pdftk-java \ + ; \ + rm -rf /var/lib/apt/lists/* +RUN set -ex; \ + \ + savedAptMark="$(apt-mark showmanual)"; \ + \ + apt-get -y update;\ + apt-get install -y --no-install-recommends \ + libc-client-dev \ + libgpgme11-dev \ + libkrb5-dev \ + libmagickwand-dev \ + libyaml-dev \ + libzip-dev \ + ; \ + pecl install gnupg \ + && \ + pecl install imagick \ + && \ + pecl install yaml \ + && \ + docker-php-ext-enable gnupg imagick yaml \ + ; \ + PHP_OPENSSL=yes docker-php-ext-configure imap --with-kerberos --with-imap-ssl \ + ; \ + docker-php-ext-configure gd \ + --with-freetype \ + --with-jpeg \ + ; \ + docker-php-ext-install \ + bcmath \ + gd \ + imap \ + intl \ + pdo_mysql \ + soap \ + zip \ + && \ + # some misbehaving extensions end up outputting to stdout 🙈 (https://github.com/docker-library/wordpress/issues/669#issuecomment-993945967) + out="$(php -r 'exit(0);')"; \ + [ -z "$out" ]; \ + err="$(php -r 'exit(0);' 3>&1 1>&2 2>&3)"; \ + [ -z "$err" ]; \ + \ + extDir="$(php -r 'echo ini_get("extension_dir");')"; \ + [ -d "$extDir" ]; \ + # reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies + apt-mark auto '.*' > /dev/null; \ + apt-mark manual $savedAptMark; \ + ldd "$extDir"/*.so \ + | awk '/=>/ { so = $(NF-1); if (index(so, "/usr/local/") == 1) { next }; gsub("^/(usr/)?", "", so); printf "*%s\n", so }' \ + | sort -u \ + | xargs -r dpkg-query --search \ + | cut -d: -f1 \ + | sort -u \ + | xargs -rt apt-mark manual; \ + \ + apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \ + rm -rf /var/lib/apt/lists/*; \ + \ + ! { ldd "$extDir"/*.so | grep 'not found'; }; \ + # check for output like "PHP Warning: PHP Startup: Unable to load dynamic library 'foo' (tried: ...) + err="$(php --version 3>&1 1>&2 2>&3)"; \ + [ -z "$err" ]; \ + rm -r /tmp/pear; \ + a2enmod rewrite headers ssl; \ + mv "$PHP_INI_DIR/php.ini-$PHPSTAGE" "$PHP_INI_DIR/php.ini" && \ + sed -i 's/upload_max_filesize = 2M/upload_max_filesize = 20M/' "$PHP_INI_DIR/php.ini" && \ + sed -i 's/post_max_size = 8M/post_max_size = 20M/' "$PHP_INI_DIR/php.ini" && \ + sed -i 's/;max_input_vars = 1000/max_input_vars = 20000/' "$PHP_INI_DIR/php.ini" + +COPY --from=docker.io/composer:2.5 /usr/bin/composer /usr/local/bin/composer +COPY config/vhost-docker /etc/apache2/sites-available/000-default.conf +ENV VRELEASE=v8.1.1 +RUN curl -fsSL -o /tmp/msehr.tar.gz https://github.com/MedShake/MedShakeEHR-base/archive/"$VRELEASE".tar.gz && \ +mkdir /usr/src/medshakeehr && \ +tar -xf /tmp/msehr.tar.gz -C /usr/src/medshakeehr --strip-components=1 && \ +rm /tmp/msehr.tar.gz +COPY config/MEDSHAKEEHRPATH-docker /usr/src/medshakeehr/public_html/MEDSHAKEEHRPATH +VOLUME /var/www/html/ +COPY msehr.entrypoint /usr/local/bin/ +COPY msehr.upgrade.php /usr/local/bin/ +ENTRYPOINT ["msehr.entrypoint"] +CMD ["apache2-foreground"] \ No newline at end of file diff --git a/tools/docker/README.md b/tools/docker/README.md new file mode 100755 index 00000000..a1112291 --- /dev/null +++ b/tools/docker/README.md @@ -0,0 +1,67 @@ +# MedShakeEHR Docker Compose + +Pile LAMP pour MedShakeEHR en local : +* PHP +* Apache +* MySQL +* phpMyAdmin +* Orthanc +* Reverse proxy +* Certificat SSL autosigné +* VPN (Wireguard) + +## Installation + +* Configurez le .env selon vos besoins. + +```bash +cp sample.env .env +nano .env +``` +* Modifiez l'image msehr de votre choix. + +```bash +nano compose.yml +medshakeehr: + image: marsante/msehrtest:x.x.x +``` + +* Ou modifiez le fichier compose avec le Dockerfile de votre choix. + +```bash +nano compose.yml +medshakeehr: + build: ./ +``` + +* Vous pouvez aussi modifier le Dockerfile avec votre clone de MedShakeEHR pour tester vos nouvelles fonctionnalités. +* Puis lancez la stack : +```bash +docker compose up --build -d +# sudo devant si docker non rootless et que l'utilisateur ne fait pas partie du groupe docker +# docker-compose up --build -d si vous avez une ancienne version de docker compose +``` +suivant votre configuration. +* Tapez [msehr.localhost/install.php](msehr.localhost/install.php) dans votre navigateur. +* Suivez les instructions. + +* Pour ajouter un module, ou le mettre à jour : + +```bash +docker exec -ti msehr php /usr/local/bin/msehr.upgrade.php base +``` + +* les arguments disponibles sont : base, chiro, gyneco, general, thermal, mpr, osteo + + +## Orthanc +* Créez le fichier de configuration `cp sample-orthanc.json orthanc.json` et éditez `nano orthanc.json` +* Relancez la stack docker compose ainsi `docker compose --profile dicom` + +## phpMyAdmin +* Relancez la stack docker compose ainsi `docker compose --profile debug` puis rendez-vous sur [pma.msehr.localhost/](pma.msehr.localhost/) + +## VPN (Wireguard) +* Modifiez le .env en personnalisant avec vos données réseaux / domaine. +* Relancez la stack docker compose ainsi `docker compose --profile vpn`. + diff --git a/tools/docker/compose.yml b/tools/docker/compose.yml new file mode 100755 index 00000000..f9648041 --- /dev/null +++ b/tools/docker/compose.yml @@ -0,0 +1,168 @@ +services: + medshakeehr: + container_name: msehr + # image: marsante/msehr:8.1.1 + build: + context: ./ + tags: + - "marsante/msehr:master" + user: ${USER_ID}:${GROUP_ID} + restart: unless-stopped + environment: + TZ: ${TZ} + VIRTUAL_HOST: ${VIRTUAL_HOST} + SELF_SIGNED_HOST: ${VIRTUAL_HOST} + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} + MYSQL_USER: ${MYSQL_USER} + MYSQL_PASSWORD: ${MYSQL_ROOT_PASSWORD} + MYSQL_DATABASE: ${MYSQL_DATABASE} + # PROTO: ${PROTO} + # COOKIED: ${COOKIED} + # FINGERPRINT: ${FINGERPRINT} + # SQLVARPSWD: ${SQLVARPSWD} + depends_on: + - db + - proxy-companion + volumes: + # - medshakeehr:/var/www/html + # if you use bind volume with arbitrary user create first the folder with the good permissions + - ../../:/var/www/html + networks: + - proxy + - db + - dicom + + # https://hub.docker.com/_/mariadb + db: + image: mariadb:10.11 + restart: unless-stopped + environment: + TZ: ${TZ} + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} + MYSQL_DATABASE: ${MYSQL_DATABASE} + MYSQL_USER: ${MYSQL_USER} + MYSQL_PASSWORD: ${MYSQL_PASSWORD} + MARIADB_AUTO_UPGRADE: ${MARIADB_AUTO_UPGRADE} + volumes: + - db-data:/var/lib/mysql + # If you would import old database + # - ./db-data:/docker-entrypoint-initdb.d + networks: + - db + + # https://hub.docker.com/_/phpmyadmin + phpmyadmin: + image: phpmyadmin + restart: unless-stopped + depends_on: + - db + - proxy-companion + environment: + VIRTUAL_HOST: "pma.${VIRTUAL_HOST}" + SELF_SIGNED_HOST: "pma.${VIRTUAL_HOST}" + PMA_HOST: db + networks: + - proxy + - db + profiles: + - debug + + # https://hub.docker.com/r/osimis/orthanc + dicom: + image: orthancteam/orthanc + restart: unless-stopped + command: /run/secrets/ # Path to the configuration files (stored as secrets) + secrets: + - orthanc.json + networks: + - dicom + profiles: + - dicom + + # https://hub.docker.com/r/jwilder/nginx-proxy + nginx-proxy: + image: nginxproxy/nginx-proxy:alpine + restart: unless-stopped + environment: + TZ: ${TZ} + ports: + - "80:80" + - "443:443" + volumes: + - certs:/etc/nginx/certs + - /var/run/docker.sock:/tmp/docker.sock:ro + # rootless socket for user 1000 + # - /run/user/1000/docker.sock:/tmp/docker.sock:ro + networks: + - proxy + + # https://hub.docker.com/r/sebastienheyd/self-signed-proxy-companion + proxy-companion: + image: sebastienheyd/self-signed-proxy-companion + restart: unless-stopped + depends_on: + - nginx-proxy + volumes: + - certs:/etc/nginx/certs + - /var/run/docker.sock:/var/run/docker.sock:ro + # rootless socket for user 1000 + # - /run/user/1000/docker.sock:/var/run/docker.sock:ro + + # https://hub.docker.com/r/containrrr/watchtower + watchtower: + image: containrrr/watchtower + restart: unless-stopped + environment: + TZ: $TZ + # WATCHTOWER_SCHEDULE: ${WATCHTOWER_SCHEDULE} + WATCHTOWER_ROLLING_RESTART: ${WATCHTOWER_ROLLING_RESTART} + WATCHTOWER_CLEANUP: ${WATCHTOWER_CLEANUP} + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + # - /run/user/1000/docker.sock:/var/run/docker.sock:ro + + # # https://hub.docker.com/r/linuxserver/wireguard + vpn: + image: lscr.io/linuxserver/wireguard:latest + cap_add: + - NET_ADMIN + - SYS_MODULE + environment: + PUID: ${USER_ID} + PGID: ${GROUP_ID} + TZ: ${TZ} + SERVERURL: #optional, public domain or IP + SERVERPORT: 51820 #optional + PEERS: 1 #optional + PEERDNS: auto #optional + INTERNAL_SUBNET: ${INTERNAL_SUBNET} #optional + ALLOWEDIPS: 0.0.0.0/0 #optional + PERSISTENTKEEPALIVE_PEERS: #optional + LOG_CONFS: true #optional + volumes: + - ${VPN_CONFIG_PATH}:/config + - /lib/modules:/lib/modules #optional + ports: + - 51820:51820/udp + sysctls: + - net.ipv4.conf.all.src_valid_mark=1 + restart: unless-stopped + networks: + - proxy + profiles: + - vpn + +secrets: + orthanc.json: + file: orthanc.json + +volumes: + db-data: + certs: + # medshakeehr: + +networks: + proxy: + name: proxy + db: + dicom: diff --git a/tools/docker/config/MEDSHAKEEHRPATH-docker b/tools/docker/config/MEDSHAKEEHRPATH-docker new file mode 100644 index 00000000..f58a7ce6 --- /dev/null +++ b/tools/docker/config/MEDSHAKEEHRPATH-docker @@ -0,0 +1 @@ +/var/www/html \ No newline at end of file diff --git a/tools/docker/config/vhost-docker b/tools/docker/config/vhost-docker new file mode 100644 index 00000000..370d1556 --- /dev/null +++ b/tools/docker/config/vhost-docker @@ -0,0 +1,35 @@ + + # The ServerName directive sets the request scheme, hostname and port that + # the server uses to identify itself. This is used when creating + # redirection URLs. In the context of virtual hosts, the ServerName + # specifies what hostname must appear in the request's Host: header to + # match this virtual host. For the default virtual host (this file) this + # value is not decisive as it is used as a last resort host regardless. + # However, you must set it for any further virtual host explicitly. + #ServerName www.example.com + + ServerAdmin webmaster@localhost + DocumentRoot /var/www/html/public_html + + AllowOverride all + Options FollowSymLinks + Require all granted + + RewriteEngine On + + # Available loglevels: trace8, ..., trace1, debug, info, notice, warn, + # error, crit, alert, emerg. + # It is also possible to configure the loglevel for particular + # modules, e.g. + #LogLevel info ssl:warn + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + # For most configuration files from conf-available/, which are + # enabled or disabled at a global level, it is possible to + # include a line for only one particular virtual host. For example the + # following line enables the CGI configuration for this host only + # after it has been globally disabled with "a2disconf". + #Include conf-available/serve-cgi-bin.conf + \ No newline at end of file diff --git a/tools/docker/msehr.entrypoint b/tools/docker/msehr.entrypoint new file mode 100755 index 00000000..4279f53f --- /dev/null +++ b/tools/docker/msehr.entrypoint @@ -0,0 +1,75 @@ +#!/bin/bash +set -e + +if [ ! -e /var/www/html/public_html/index.php ]; then + echo "Copie de MedShakeEHR ..." + cp -pr /usr/src/medshakeehr/* /var/www/html/ + cd /var/www/html +fi + +if [ ! -e /var/www/html/public_html/MEDSHAKEEHRPATH ]; then + echo "Copie MEDSHAKEEHRPATH" + echo " /var/www/html + " > /var/www/html/public_html/MEDSHAKEEHRPATH +fi + +composer install -n --no-plugins --no-scripts --no-cache --no-ansi --no-progress --no-dev -o && \ +cd public_html && \ +composer install -n --no-plugins --no-scripts --no-cache --no-ansi --no-progress --no-dev -o + +if [ ! -e /var/www/html/config/config.yml ]; then + echo "Configuration absente, création ..." + + generate_random_string() { + openssl rand -base64 12 + } + + set_default_value() { + if [ -z "$1" ]; then + echo "$2" + else + echo "$1" + fi + } + + FINGERPRINT=$(set_default_value "$FINGERPRINT" "$(generate_random_string)") + SQLVARPSWD=$(set_default_value "$SQLVARPSWD" "$(generate_random_string)") + PROTO=$(set_default_value "$PROTO" "https") + PORT=$(set_default_value "$PORT" "") + COOKIED=$(set_default_value "$COOKIED" "31104000") + + echo " +--- +sqlRootId: "" +sqlRootPwd: "" +sqlNotCreatDb: true +protocol: $PROTO:// +host: $VIRTUAL_HOST +port: $PORT +urlHostSuffixe: "" +webDirectory: /var/www/html/public_html/ +stockageLocation: /var/www/html/stockage/ +backupLocation: /var/www/html/backups/ +workingDirectory: /var/www/html/public_html/workingDirectory/ +cookieDomain: $VIRTUAL_HOST +cookieDuration: $COOKIED +fingerprint: $FINGERPRINT +sqlServeur: db +sqlBase: $MYSQL_DATABASE +sqlUser: $MYSQL_USER +sqlPass: $MYSQL_PASSWORD +sqlVarPassword: $SQLVARPSWD +templatesFolder: /var/www/html/templates/ +twigEnvironnementCache: false +twigEnvironnementAutoescape: false +twigDebug: false +configForm: "" +... + " > /var/www/html/config/config.yml +fi + +uid="$(id -u)" +if [ "$uid" = 0 ]; then + chown -R www-data:www-data /var/www/html/* +fi +exec "$@" \ No newline at end of file diff --git a/tools/docker/msehr.upgrade.php b/tools/docker/msehr.upgrade.php new file mode 100755 index 00000000..6eb18a91 --- /dev/null +++ b/tools/docker/msehr.upgrade.php @@ -0,0 +1,104 @@ +\n\n base, chiro, gyneco, general, thermal, mpr, osteo\n"; + exit(1); +} + +// Extract the shorthand repo argument +$repoShorthand = $argv[1]; + +// Define a mapping of shorthand names to GitHub user/repo +$repoMap = [ + 'base' => 'MedShake/MedShakeEHR-base', + 'chiro' => 'MedShake/MedShakeEHR-modChiro', + 'gyneco' => 'MedShake/MedShakeEHR-modGynObs', + 'general' => 'MedShake/MedShakeEHR-modMedGe', + 'thermal' => 'MedShake/MedShakeEHR-modMedTher', + 'mpr' => 'MedShake/MedShakeEHR-modMPR', + 'osteo' => 'marsante/MedShakeEHR-modOsteo', + +]; + +// Check if the repo shorthand exists in the map +if (!isset($repoMap[$repoShorthand])) { + echo "Invalid repo shorthand.\n"; + exit(1); +} + +// Extract the user and repo from the repo map +$repoInfo = explode('/', $repoMap[$repoShorthand]); +$user = $repoInfo[0]; +$repo = $repoInfo[1]; + +try { + // Extract the database credentials from config.yml + $configFile = '/var/www/html/config/config.yml'; + $config = yaml_parse_file($configFile); + + $sqlServer = $config['sqlServeur']; + $sqlBase = $config['sqlBase']; + $sqlUser = $config['sqlUser']; + $sqlPass = $config['sqlPass']; + + // Update the value of the 'state' column in the 'system' table + $dsn = "mysql:host=$sqlServer;dbname=$sqlBase;charset=utf8mb4"; + $pdo = new PDO($dsn, $sqlUser, $sqlPass); + + // Set PDO error mode to exception + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + $sql = "UPDATE system SET value = 'maintenance' WHERE name = 'state'"; + $pdo->exec($sql); + + // Retrieve the latest release tag name using cURL + $apiUrl = "https://api.github.com/repos/$user/$repo/releases/latest"; + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $apiUrl); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HEADER, false); + curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'); + $response = curl_exec($ch); + curl_close($ch); + + $releaseData = json_decode($response, true); + $latestRelease = $releaseData['tag_name']; + + // Print the latest release tag name + echo "Dernière version : $latestRelease\n"; + + // Download and extract the release from GitHub + $version = substr($latestRelease, 1); // Remove the 'v' prefix + $tarFile = "/tmp/$latestRelease.tar.gz"; + $downloadUrl = "https://github.com/$user/$repo/archive/$latestRelease.tar.gz"; + + // Download the release tar.gz file using cURL + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $downloadUrl); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + $response = curl_exec($ch); + curl_close($ch); + + // Save the downloaded tar.gz file + file_put_contents($tarFile, $response); + + // Extract the release contents using tar + $extractDir = "/tmp/$repo-$version"; + $extractCommand = "tar -xzf $tarFile -C /tmp"; + exec($extractCommand); + + // Move the extracted files to the target directory + $moveCommand = "cp -r -f $extractDir/* /var/www/html/"; + exec($moveCommand); + + // Print a success message + echo "La copie c'est bien déroulée, connectez vous à votre compte administrateur pour appliquer la mise à jour.\n"; + +} catch (Exception $e) { + // Handle any errors + echo "Erreur: " . $e->getMessage() . "\n"; +} + +?> \ No newline at end of file diff --git a/tools/docker/sample-orthanc.json b/tools/docker/sample-orthanc.json new file mode 100755 index 00000000..ecc5695a --- /dev/null +++ b/tools/docker/sample-orthanc.json @@ -0,0 +1,5 @@ +{ + "Name" : "orthanc pour MedShakeEHR", + "AuthenticationEnabled": false, + "RemoteAccessAllowed" : true + } \ No newline at end of file diff --git a/tools/docker/sample.env b/tools/docker/sample.env new file mode 100755 index 00000000..7dfff3da --- /dev/null +++ b/tools/docker/sample.env @@ -0,0 +1,24 @@ +TZ=Europe/Paris + +MYSQL_ROOT_PASSWORD=PASSWORD +MYSQL_USER=docker +MYSQL_PASSWORD=PASSWORD +MYSQL_DATABASE=medshakeehr +MARIADB_AUTO_UPGRADE=1 + +VIRTUAL_HOST=msehr.localhost +PROTO=https +PORT=”” +COOKIED=31104000 +FINGERPRINT=03Akxqa66Yw +SQLVARPSWD=3Hcha5n9Bs +USER_ID=1000 +GROUP_ID=1000 + +VPN_CONFIG_PATH=vpn-config +VPN_DOM= wireguard.domain.com +INTERNAL_SUBNET=10.13.13.0 + +WATCHTOWER_SCHEDULE= 0 * 2 * * * +WATCHTOWER_ROLLING_RESTART=true +WATCHTOWER_CLEANUP=true