diff --git a/conf/passbolt.conf b/conf/passbolt.conf new file mode 100644 index 0000000..7b3f453 --- /dev/null +++ b/conf/passbolt.conf @@ -0,0 +1,76 @@ +server { + listen 80; + + client_body_buffer_size 100K; + client_header_buffer_size 1K; + client_max_body_size 5M; + + client_body_timeout 10; + client_header_timeout 10; + keepalive_timeout 5 5; + send_timeout 10; + + root /var/www/passbolt/webroot; + index index.php; + error_log /dev/stdout info; + access_log /dev/stdout; + + location / { + try_files $uri $uri/ /index.php?$args; + } + + location ~ \.php$ { + try_files $uri =404; + include fastcgi_params; + fastcgi_pass 127.0.0.1:9000; + fastcgi_index index.php; + fastcgi_intercept_errors on; + fastcgi_split_path_info ^(.+\.php)(.+)$; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param SERVER_NAME $http_host; + fastcgi_param PHP_VALUE "upload_max_filesize=5M \n post_max_size=5M"; + } + +} + +server { + listen 443; + + client_body_buffer_size 100K; + client_header_buffer_size 1K; + client_max_body_size 5M; + + client_body_timeout 10; + client_header_timeout 10; + keepalive_timeout 5 5; + send_timeout 10; + + ssl on; + ssl_certificate /etc/ssl/certs/certificate.crt; + ssl_certificate_key /etc/ssl/certs/certificate.key; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_prefer_server_ciphers on; + ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4:@STRENGTH"; + ssl_session_tickets off; + + root /var/www/passbolt/webroot; + index index.php; + error_log /dev/stdout info; + access_log /dev/stdout; + + location / { + try_files $uri $uri/ /index.php?$args; + } + + location ~ \.php$ { + try_files $uri =404; + include fastcgi_params; + fastcgi_pass 127.0.0.1:9000; + fastcgi_index index.php; + fastcgi_intercept_errors on; + fastcgi_split_path_info ^(.+\.php)(.+)$; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param SERVER_NAME $http_host; + fastcgi_param PHP_VALUE "upload_max_filesize=5M \n post_max_size=5M"; + } +} diff --git a/conf/supervisor/cron.conf b/conf/supervisor/cron.conf new file mode 100644 index 0000000..07bce67 --- /dev/null +++ b/conf/supervisor/cron.conf @@ -0,0 +1,8 @@ +[program:cron] +command=/bin/bash -c "declare -p | grep -Ev 'BASHOPTS|BASH_VERSINFO|EUID|PPID|SHELLOPTS|UID' > /etc/environment; cron -f -l" +autostart=true +priority=20 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 diff --git a/debian/Dockerfile b/debian/Dockerfile new file mode 100644 index 0000000..337959c --- /dev/null +++ b/debian/Dockerfile @@ -0,0 +1,48 @@ +FROM debian:buster-slim + +LABEL maintainer="Passbolt SA " + +ENV PASSBOLT_PKG_KEY=0xDE8B853FC155581D +ENV PHP_VERSION=7.3 +ENV GNUPGHOME=/var/lib/passbolt/.gnupg + +ARG PASSBOLT_REPO_URL="https://download.passbolt.com/ce/debian" +ARG PASSBOLT_DISTRO="buster" +ARG PASSBOLT_COMPONENT="stable" +ARG PASSBOLT_PKG=passbolt-ce-server + +RUN apt-get update \ + && DEBIAN_FRONTEND=non-interactive apt-get -y install \ + ca-certificates \ + gnupg \ + && apt-key adv --keyserver keys.gnupg.net --recv-keys $PASSBOLT_PKG_KEY \ + && echo "deb $PASSBOLT_REPO_URL $PASSBOLT_DISTRO $PASSBOLT_COMPONENT" > /etc/apt/sources.list.d/passbolt.list \ + && apt-get update \ + && DEBIAN_FRONTEND=non-interactive apt-get -y install --no-install-recommends \ + nginx \ + $PASSBOLT_PKG \ + supervisor \ + && rm /etc/nginx/sites-enabled/default \ + && mkdir /run/php \ + && cp /usr/share/passbolt/examples/nginx-passbolt-ssl.conf /etc/nginx/snippets/passbolt-ssl.conf \ + && sed -i 's,;clear_env = no,clear_env = no,' /etc/php/$PHP_VERSION/fpm/pool.d/www.conf \ + && sed -i 's,# include __PASSBOLT_SSL__,include /etc/nginx/snippets/passbolt-ssl.conf;,' /etc/nginx/sites-enabled/nginx-passbolt.conf \ + && sed -i '/listen \[\:\:\]\:443 ssl http2;/a listen 443 ssl http2;' /etc/nginx/snippets/passbolt-ssl.conf \ + && sed -i 's,__CERT_PATH__,/etc/ssl/certs/certificate.crt;,' /etc/nginx/snippets/passbolt-ssl.conf \ + && sed -i 's,__KEY_PATH__,/etc/ssl/certs/certificate.key;,' /etc/nginx/snippets/passbolt-ssl.conf \ + && sed -i 's,www-data.*$,www-data exec /bin/bash -c ". /etc/environment \&\& $PASSBOLT_BASE_DIR/bin/cron",' /etc/cron.d/$PASSBOLT_PKG \ + && ln -sf /dev/stdout /var/log/nginx/passbolt-access.log \ + && ln -sf /dev/stderr /var/log/nginx/passbolt-error.log \ + && ln -sf /dev/stderr /var/log/passbolt/error.log \ + && ln -sf /dev/stderr /var/log/php7.3-fpm.log \ + && crontab /etc/cron.d/$PASSBOLT_PKG + +COPY conf/supervisor/*.conf /etc/supervisor/conf.d/ +COPY debian/bin/docker-entrypoint.sh /docker-entrypoint.sh +COPY scripts/wait-for.sh /usr/bin/wait-for.sh + +EXPOSE 80 443 + +WORKDIR /usr/share/php/passbolt + +CMD ["/docker-entrypoint.sh"] diff --git a/Dockerfile b/debian/Dockerfile.rootless similarity index 83% rename from Dockerfile rename to debian/Dockerfile.rootless index 6bb71d0..9a1fd7a 100644 --- a/Dockerfile +++ b/debian/Dockerfile.rootless @@ -3,23 +3,25 @@ FROM debian:buster-slim LABEL maintainer="Passbolt SA " ENV PASSBOLT_PKG_KEY=0xDE8B853FC155581D -ENV PASSBOLT_PKG=passbolt-ce-server ENV PHP_VERSION=7.3 ENV GNUPGHOME=/var/lib/passbolt/.gnupg +ARG PASSBOLT_REPO_URL="https://download.passbolt.com/ce/debian" +ARG PASSBOLT_DISTRO="buster" +ARG PASSBOLT_COMPONENT="stable" +ARG PASSBOLT_PKG=passbolt-ce-server RUN apt-get update \ && DEBIAN_FRONTEND=non-interactive apt-get -y install \ ca-certificates \ gnupg \ && apt-key adv --keyserver keys.gnupg.net --recv-keys $PASSBOLT_PKG_KEY \ - && echo "deb https://download.passbolt.com/ce/debian buster stable" > /etc/apt/sources.list.d/passbolt.list \ + && echo "deb $PASSBOLT_REPO_URL $PASSBOLT_DISTRO $PASSBOLT_COMPONENT" > /etc/apt/sources.list.d/passbolt.list \ && apt-get update \ && DEBIAN_FRONTEND=non-interactive apt-get -y install --no-install-recommends \ nginx \ $PASSBOLT_PKG \ - supervisor \ - php-apcu + supervisor RUN sed -i 's,listen 80;,listen 8080;,' /etc/nginx/sites-enabled/nginx-passbolt.conf \ && rm /etc/nginx/sites-enabled/default \ @@ -52,14 +54,18 @@ RUN sed -i 's,listen 80;,listen 8080;,' /etc/nginx/sites-enabled/nginx-passbolt. && ln -sf /dev/stderr /var/log/php7.3-fpm.log \ && chown -R www-data:0 /var/log/supervisor \ && touch /var/www/.profile \ - && chown www-data:www-data /var/www/.profile + && chown www-data:www-data /var/www/.profile \ + && sed -i 's,www-data.*$,www-data exec /bin/bash -c ". /etc/environment \&\& $PASSBOLT_BASE_DIR/bin/cron",' /etc/cron.d/$PASSBOLT_PKG \ + && crontab /etc/cron.d/$PASSBOLT_PKG COPY conf/supervisor/*.conf /etc/supervisor/conf.d/ -COPY bin/docker-entrypoint.sh /docker-entrypoint.sh +COPY debian/bin/docker-entrypoint.sh /docker-entrypoint.sh COPY scripts/wait-for.sh /usr/bin/wait-for.sh EXPOSE 8080 4443 +WORKDIR /usr/share/php/passbolt + USER www-data CMD ["/docker-entrypoint.sh"] diff --git a/debian/bin/docker-entrypoint.sh b/debian/bin/docker-entrypoint.sh new file mode 100755 index 0000000..5c1f803 --- /dev/null +++ b/debian/bin/docker-entrypoint.sh @@ -0,0 +1,141 @@ +#!/usr/bin/env bash + +set -eo pipefail + +passbolt_config="/etc/passbolt" +gpg_private_key="${PASSBOLT_GPG_SERVER_KEY_PRIVATE:-$passbolt_config/gpg/serverkey_private.asc}" +gpg_public_key="${PASSBOLT_GPG_SERVER_KEY_PUBLIC:-$passbolt_config/gpg/serverkey.asc}" + +ssl_key='/etc/ssl/certs/certificate.key' +ssl_cert='/etc/ssl/certs/certificate.crt' + +deprecation_message="" + +entropy_check() { + local entropy_avail + + entropy_avail=$(cat /proc/sys/kernel/random/entropy_avail) + + if [ "$entropy_avail" -lt 2000 ]; then + + cat < $gpg_private_key" -ls /bin/bash www-data + su -c "gpg --homedir $GNUPGHOME --armor --export $key_email > $gpg_public_key" -ls /bin/bash www-data +} + +gpg_import_key() { + su -c "gpg --homedir $GNUPGHOME --batch --import $gpg_public_key" -ls /bin/bash www-data + su -c "gpg --homedir $GNUPGHOME --batch --import $gpg_private_key" -ls /bin/bash www-data +} + +gen_ssl_cert() { + openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 \ + -subj '/C=FR/ST=Denial/L=Springfield/O=Dis/CN=www.passbolt.local' \ + -keyout $ssl_key -out $ssl_cert +} + +install() { + if [ ! -f "$passbolt_config/app.php" ]; then + su -c "cp $passbolt_config/app.default.php $passbolt_config/app.php" -s /bin/bash www-data + fi + + if [ -z "${PASSBOLT_GPG_SERVER_KEY_FINGERPRINT+xxx}" ] && [ ! -f "$passbolt_config/passbolt.php" ]; then + gpg_auto_fingerprint="$(su -c "gpg --homedir $GNUPGHOME --list-keys --with-colons ${PASSBOLT_KEY_EMAIL:-passbolt@yourdomain.com} |grep fpr |head -1| cut -f10 -d:" -ls /bin/bash www-data)" + export PASSBOLT_GPG_SERVER_KEY_FINGERPRINT=$gpg_auto_fingerprint + fi + + su -c '/usr/share/php/passbolt/bin/cake passbolt install --no-admin' -s /bin/bash www-data || su -c '/usr/share/php/passbolt/bin/cake passbolt migrate' -s /bin/bash www-data && echo "Enjoy! ☮" +} + +create_deprecation_message() { + deprecation_message+="\033[33;5;7mWARNING: $1 is deprecated, point your docker volume to $2\033[0m\n" +} + +check_deprecated_paths() { + declare -A deprecated_paths + local deprecated_avatar_path="/var/www/passbolt/webroot/img/public/Avatar" + local avatar_path="/usr/share/php/passbolt/webroot/img/public/Avatar" + local deprecated_subscription_path="/var/www/passbolt/webroot/img/public/Avatar" + local subscription_path="/etc/passbolt/license" + deprecated_paths=( + ['/var/www/passbolt/config/gpg/serverkey.asc']='/etc/passbolt/gpg/serverkey.asc' + ['/var/www/passbolt/config/gpg/serverkey_private.asc']='/etc/passbolt/gpg/serverkey_private.asc' + ) + + if [ -z "$PASSBOLT_GPG_SERVER_KEY_PUBLIC" ] || [ -z "$PASSBOLT_GPG_SERVER_KEY_PRIVATE" ]; then + for path in "${!deprecated_paths[@]}" + do + if [ -f "$path" ] && [ ! -f "${deprecated_paths[$path]}" ]; then + ln -s "$path" "${deprecated_paths[$path]}" + create_deprecation_message "$path" "${deprecated_paths[$path]}" + fi + done + fi + + if [ -d "$deprecated_avatar_path" ] && [ ! -d "$avatar_path" ]; then + ln -s "$deprecated_avatar_path" "$avatar_path" + create_deprecation_message "$deprecated_avatar_path" "$avatar_path" + fi + + if [ -f "$deprecated_subscription_path" ] && [ ! -f "$subscription_path" ]; then + ln -s "$deprecated_subscription_path" "$subscription_path" + create_deprecation_message "$deprecated_subscription_path" "$subscription_path" + fi +} + +check_deprecated_paths + +if [ ! -f "$gpg_private_key" ] || \ + [ ! -f "$gpg_public_key" ]; then + gpg_gen_key + gpg_import_key +else + gpg_import_key +fi + +if [ ! -f "$ssl_key" ] && [ ! -L "$ssl_key" ] && \ + [ ! -f "$ssl_cert" ] && [ ! -L "$ssl_cert" ]; then + gen_ssl_cert +fi + +install + +echo -e "$deprecation_message" + +exec /usr/bin/supervisord -n diff --git a/debian/conf b/debian/conf new file mode 120000 index 0000000..59f0502 --- /dev/null +++ b/debian/conf @@ -0,0 +1 @@ +../conf \ No newline at end of file diff --git a/debian/scripts b/debian/scripts new file mode 120000 index 0000000..a339954 --- /dev/null +++ b/debian/scripts @@ -0,0 +1 @@ +../scripts \ No newline at end of file diff --git a/dev/Dockerfile b/dev/Dockerfile new file mode 100644 index 0000000..f631edf --- /dev/null +++ b/dev/Dockerfile @@ -0,0 +1,91 @@ +FROM php:7.3.24-fpm + +LABEL maintainer="Passbolt SA " + +ARG PASSBOLT_VERSION="2.13.5" +ARG PASSBOLT_URL="https://github.com/passbolt/passbolt_api/archive/v${PASSBOLT_VERSION}.tar.gz" +ARG PASSBOLT_CURL_HEADERS="" + +ARG PHP_EXTENSIONS="gd \ + intl \ + pdo_mysql \ + opcache \ + xsl" + +ARG PECL_PASSBOLT_EXTENSIONS="gnupg \ + redis \ + mcrypt" + +ARG PASSBOLT_DEV_PACKAGES="libgpgme11-dev \ + libpng-dev \ + libjpeg62-turbo-dev \ + libicu-dev \ + libxslt1-dev \ + libmcrypt-dev \ + unzip" + +ARG PASSBOLT_BASE_PACKAGES="nginx \ + gnupg \ + libgpgme11 \ + libmcrypt4 \ + mariadb-client \ + supervisor \ + cron" + +ENV PECL_BASE_URL="https://pecl.php.net/get" +ENV PHP_EXT_DIR="/usr/src/php/ext" + +WORKDIR /var/www/passbolt +RUN apt-get update \ + && apt-get -y install --no-install-recommends \ + $PASSBOLT_DEV_PACKAGES \ + $PASSBOLT_BASE_PACKAGES \ + && mkdir /home/www-data \ + && chown -R www-data:www-data /home/www-data \ + && usermod -d /home/www-data www-data \ + && docker-php-source extract \ + && for i in $PECL_PASSBOLT_EXTENSIONS; do \ + mkdir $PHP_EXT_DIR/$i; \ + curl -sSL $PECL_BASE_URL/$i | tar zxf - -C $PHP_EXT_DIR/$i --strip-components 1; \ + done \ + && docker-php-ext-configure gd --with-jpeg-dir=/usr/include/ \ + && docker-php-ext-install -j4 $PHP_EXTENSIONS $PECL_PASSBOLT_EXTENSIONS \ + && docker-php-ext-enable $PHP_EXTENSIONS $PECL_PASSBOLT_EXTENSIONS \ + && docker-php-source delete \ + && EXPECTED_SIGNATURE=$(curl -s https://composer.github.io/installer.sig) \ + && curl -o composer-setup.php https://getcomposer.org/installer \ + && ACTUAL_SIGNATURE=$(php -r "echo hash_file('SHA384', 'composer-setup.php');") \ + && if [ "$EXPECTED_SIGNATURE" != "$ACTUAL_SIGNATURE" ]; then \ + >&2 echo 'ERROR: Invalid installer signature'; \ + rm composer-setup.php; \ + exit 1; \ + fi \ + && php composer-setup.php --1 \ + && mv composer.phar /usr/local/bin/composer \ + && rm composer-setup.php \ + && curl -sSL -H "$PASSBOLT_CURL_HEADERS" "$PASSBOLT_URL" | tar zxf - -C . --strip-components 1 \ + && composer install -n --no-dev --optimize-autoloader \ + && chown -R www-data:www-data . \ + && chmod 775 $(find /var/www/passbolt/tmp -type d) \ + && chmod 664 $(find /var/www/passbolt/tmp -type f) \ + && chmod 775 $(find /var/www/passbolt/webroot/img/public -type d) \ + && chmod 664 $(find /var/www/passbolt/webroot/img/public -type f) \ + && rm /etc/nginx/sites-enabled/default \ + && apt-get purge -y --auto-remove $PASSBOLT_DEV_PACKAGES \ + && rm -rf /var/lib/apt/lists/* \ + && rm /usr/local/bin/composer \ + && echo 'php_flag[expose_php] = off' > /usr/local/etc/php-fpm.d/expose.conf \ + && sed -i 's/# server_tokens/server_tokens/' /etc/nginx/nginx.conf \ + && mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" \ + && echo "* * * * * su -c \"source /etc/environment ; /var/www/passbolt/bin/cake EmailQueue.sender\" -s /bin/bash www-data >> /var/log/cron.log 2>&1" >> /etc/cron.d/passbolt_email \ + && crontab /etc/cron.d/passbolt_email \ + && ln -s $(which php-fpm){,7.3} + +COPY conf/passbolt.conf /etc/nginx/conf.d/default.conf +COPY conf/supervisor/*.conf /etc/supervisor/conf.d/ +COPY dev/bin/docker-entrypoint.sh /docker-entrypoint.sh +COPY scripts/wait-for.sh /usr/bin/wait-for.sh + +EXPOSE 80 443 + +CMD ["/docker-entrypoint.sh"] diff --git a/bin/docker-entrypoint.sh b/dev/bin/docker-entrypoint.sh similarity index 61% rename from bin/docker-entrypoint.sh rename to dev/bin/docker-entrypoint.sh index c095df4..09784b4 100755 --- a/bin/docker-entrypoint.sh +++ b/dev/bin/docker-entrypoint.sh @@ -2,15 +2,13 @@ set -euo pipefail -passbolt_config="/etc/passbolt" -passbolt_base="/usr/share/php/passbolt" -gpg_private_key="${PASSBOLT_GPG_SERVER_KEY_PRIVATE:-$passbolt_config/gpg/serverkey_private.asc}" -gpg_public_key="${PASSBOLT_GPG_SERVER_KEY_PUBLIC:-$passbolt_config/gpg/serverkey.asc}" +gpg_private_key="${PASSBOLT_GPG_SERVER_KEY_PRIVATE:-/var/www/passbolt/config/gpg/serverkey_private.asc}" +gpg_public_key="${PASSBOLT_GPG_SERVER_KEY_PUBLIC:-/var/www/passbolt/config/gpg/serverkey.asc}" -ssl_key="$passbolt_config/certs/certificate.key" -ssl_cert="$passbolt_config/certs/certificate.crt" +ssl_key='/etc/ssl/certs/certificate.key' +ssl_cert='/etc/ssl/certs/certificate.crt' -export GNUPGHOME="/var/lib/passbolt/.gnupg" +export GNUPGHOME="/home/www-data/.gnupg" entropy_check() { local entropy_avail @@ -44,7 +42,7 @@ gpg_gen_key() { entropy_check - gpg --batch --no-tty --gen-key < "$gpg_private_key" - gpg --armor --export "$key_email" > "$gpg_public_key" + su -c "gpg --armor --export-secret-keys $key_email > $gpg_private_key" -ls /bin/bash www-data + su -c "gpg --armor --export $key_email > $gpg_public_key" -ls /bin/bash www-data } gpg_import_key() { - gpg --batch --import "$gpg_public_key" - gpg --batch --import "$gpg_private_key" + su -c "gpg --batch --import $gpg_public_key" -ls /bin/bash www-data + su -c "gpg --batch --import $gpg_private_key" -ls /bin/bash www-data } gen_ssl_cert() { openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 \ -subj '/C=FR/ST=Denial/L=Springfield/O=Dis/CN=www.passbolt.local' \ - -keyout "$ssl_key" -out "$ssl_cert" + -keyout $ssl_key -out $ssl_cert } install() { - if [ -z "${PASSBOLT_GPG_SERVER_KEY_FINGERPRINT+xxx}" ] && [ ! -f "$passbolt_config/passbolt.php" ]; then - gpg_auto_fingerprint="$(gpg --homedir $GNUPGHOME --list-keys --with-colons ${PASSBOLT_KEY_EMAIL:-passbolt@yourdomain.com} |grep fpr |head -1| cut -f10 -d:)" + local app_config="/var/www/passbolt/config/app.php" + + if [ ! -f "$app_config" ]; then + su -c 'cp /var/www/passbolt/config/app.default.php /var/www/passbolt/config/app.php' -s /bin/bash www-data + fi + + if [ -z "${PASSBOLT_GPG_SERVER_KEY_FINGERPRINT+xxx}" ] && [ ! -f '/var/www/passbolt/config/passbolt.php' ]; then + gpg_auto_fingerprint="$(su -c "gpg --list-keys --with-colons ${PASSBOLT_KEY_EMAIL:-passbolt@yourdomain.com} |grep fpr |head -1| cut -f10 -d:" -ls /bin/bash www-data)" export PASSBOLT_GPG_SERVER_KEY_FINGERPRINT=$gpg_auto_fingerprint fi - $passbolt_base/bin/cake passbolt install --no-admin || $passbolt_base/bin/cake passbolt migrate && echo "Enjoy! ☮" + su -c '/var/www/passbolt/bin/cake passbolt install --no-admin' -s /bin/bash www-data || su -c '/var/www/passbolt/bin/cake passbolt migrate' -s /bin/bash www-data && echo "Enjoy! ☮" } - if [ ! -f "$gpg_private_key" ] && [ ! -L "$gpg_private_key" ] || \ [ ! -f "$gpg_public_key" ] && [ ! -L "$gpg_public_key" ]; then gpg_gen_key diff --git a/dev/conf b/dev/conf new file mode 120000 index 0000000..59f0502 --- /dev/null +++ b/dev/conf @@ -0,0 +1 @@ +../conf \ No newline at end of file diff --git a/dev/scripts b/dev/scripts new file mode 120000 index 0000000..a339954 --- /dev/null +++ b/dev/scripts @@ -0,0 +1 @@ +../scripts \ No newline at end of file