From d1fac583ea0fe2fa5853dcbcf5d0205d3c9f7480 Mon Sep 17 00:00:00 2001 From: Shuro <944030+Shuro@users.noreply.github.com> Date: Mon, 25 Jan 2021 01:28:50 +0100 Subject: [PATCH 01/16] Use configured default page also for IPv6 Just a small check for the ipv6 variable, similar to _listen.conf, so that the configured default page is also delivered on ipv6 requests. --- backend/templates/default.conf | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/templates/default.conf b/backend/templates/default.conf index 7ed1af978..56b67090e 100644 --- a/backend/templates/default.conf +++ b/backend/templates/default.conf @@ -6,6 +6,11 @@ {%- else %} server { listen 80 default; +{% if ipv6 -%} + listen [::]:80; +{% else -%} + #listen [::]:80; +{% endif %} server_name default-host.localhost; access_log /data/logs/default_host.log combined; {% include "_exploits.conf" %} From 4ac52a0e25578fe16605e9bccc780f8ad0f16d4f Mon Sep 17 00:00:00 2001 From: MooBaloo Date: Thu, 28 Jan 2021 05:52:41 -0500 Subject: [PATCH 02/16] Add custom .conf above includes for NPM-generated files. Added a new clause for custom http_top.conf above the include clauses for NPM-generated files. Allows for more flexibility with adding custom nginx .conf files to NPM Use case: adding a configuration change needs to be present before other custom configuration files are called and reference configuration from the custom http_top.conf file. Example: add a new log_format in http_top.conf, then referencing it in a access_log clause in server_proxy.conf. --- docker/rootfs/etc/nginx/nginx.conf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker/rootfs/etc/nginx/nginx.conf b/docker/rootfs/etc/nginx/nginx.conf index 4524d2d23..05ea6f408 100644 --- a/docker/rootfs/etc/nginx/nginx.conf +++ b/docker/rootfs/etc/nginx/nginx.conf @@ -69,6 +69,9 @@ http { real_ip_header X-Real-IP; real_ip_recursive on; + # Custom + include /data/nginx/custom/http_top[.]conf; + # Files generated by NPM include /etc/nginx/conf.d/*.conf; include /data/nginx/default_host/*.conf; From d6c344b5ecc23a0ef510dcd6ee18b774b5d1617e Mon Sep 17 00:00:00 2001 From: ahgraber Date: Tue, 2 Feb 2021 08:58:45 -0500 Subject: [PATCH 03/16] add local docker-compose to build --- docker/docker-compose.yaml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 docker/docker-compose.yaml diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml new file mode 100644 index 000000000..a7f4c10f6 --- /dev/null +++ b/docker/docker-compose.yaml @@ -0,0 +1,24 @@ +# docker-compose.yml +# RUN: ```docker-compose --env-file``` +version: "3.7" + +services: + npm: + build: + context: ../ + dockerfile: ./docker/Dockerfile + args: + VERSION: 2.7.3 #"${NPM_VERSION}" # if using .env file + image: npm:test # provide a name and tag for the image + # mariadb: + # build: + # context: . + # dockerfile: Dockerfile-mariadb + # args: + # VERSION: 10.4.15 #"${MARIADB_VERSION}" # if using .env file + # image: mariadb:test + + + # volumes: # Optional, same as `docker volume create` + + # networks: # Optional, same as `docker network create` \ No newline at end of file From 64761ee9c64da2529ff02723e1d285ba372337a5 Mon Sep 17 00:00:00 2001 From: Alex Graber Date: Thu, 4 Feb 2021 11:15:31 -0500 Subject: [PATCH 04/16] beta secrets --- docker/dev/Dockerfile | 2 +- docker/dev/docker-compose.yaml | 49 ++++++++++++++++++++++++++++++++++ docker/docker-compose.yaml | 24 ----------------- 3 files changed, 50 insertions(+), 25 deletions(-) create mode 100644 docker/dev/docker-compose.yaml delete mode 100644 docker/docker-compose.yaml diff --git a/docker/dev/Dockerfile b/docker/dev/Dockerfile index 45ee534ca..9ac2911f1 100644 --- a/docker/dev/Dockerfile +++ b/docker/dev/Dockerfile @@ -27,6 +27,6 @@ EXPOSE 80 EXPOSE 81 EXPOSE 443 -CMD [ "/init" ] +ENTRYPOINT [ "/init" ] HEALTHCHECK --interval=5s --timeout=3s CMD /bin/check-health diff --git a/docker/dev/docker-compose.yaml b/docker/dev/docker-compose.yaml new file mode 100644 index 000000000..46322a48d --- /dev/null +++ b/docker/dev/docker-compose.yaml @@ -0,0 +1,49 @@ +# docker-compose.yml +# RUN: ```docker-compose --env-file``` +version: "3.7" + +secrets: + # paths assume the .secrets folder is sibling depth to folder where docker-compose resides + DB_ROOT_PWD: + file: ../../../homelab-setup/docker_secrets/db_root_pwd.txt + MYSQL_PWD: + file: ../../../homelab-setup/docker_secrets/mysql_pwd.txt + +services: + mariadb: + image: mariadb:test + container_name: mariadb + secrets: + - DB_ROOT_PWD + - MYSQL_PWD + environment: + # MYSQL_ROOT_PASSWORD: "npm" + MYSQL_ROOT_PASSWORD__FILE: /run/secrets/DB_ROOT_PWD + MYSQL_DATABASE: "npm" + MYSQL_USER: "npm" + # MYSQL_PASSWORD: "npm" + MYSQL_PASSWORD__FILE: /run/secrets/MYSQL_PWD + volumes: + - ./data/mysql:/var/lib/mysql + + npm: + build: + context: ../../ + dockerfile: ./docker/Dockerfile + image: npm:test # provide a name and tag for the image + container_name: npm + secrets: + - MYSQL_PWD + environment: + DISABLE_IPV6: 'true' + DB_MYSQL_HOST: "mariadb" + DB_MYSQL_PORT: 3306 + MYSQL_DATABASE: "npm" + MYSQL_USER: "npm" + # MYSQL_PASSWORD: "npm" + MYSQL_PASSWORD__FILE: /run/secrets/MYSQL_PWD + volumes: + - ./data/npm:/data + - ./data/letsencrypt:/etc/letsencrypt + depends_on: + - mariadb \ No newline at end of file diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml deleted file mode 100644 index a7f4c10f6..000000000 --- a/docker/docker-compose.yaml +++ /dev/null @@ -1,24 +0,0 @@ -# docker-compose.yml -# RUN: ```docker-compose --env-file``` -version: "3.7" - -services: - npm: - build: - context: ../ - dockerfile: ./docker/Dockerfile - args: - VERSION: 2.7.3 #"${NPM_VERSION}" # if using .env file - image: npm:test # provide a name and tag for the image - # mariadb: - # build: - # context: . - # dockerfile: Dockerfile-mariadb - # args: - # VERSION: 10.4.15 #"${MARIADB_VERSION}" # if using .env file - # image: mariadb:test - - - # volumes: # Optional, same as `docker volume create` - - # networks: # Optional, same as `docker network create` \ No newline at end of file From 63a71afbc8aecb00de5f0abd48e15dc7c224dcaa Mon Sep 17 00:00:00 2001 From: Alex Graber Date: Thu, 4 Feb 2021 11:25:26 -0500 Subject: [PATCH 05/16] beta s6 secrets --- docker/rootfs/etc/cont-init.d/.gitignore | 1 + docker/rootfs/etc/cont-init.d/01_envfile.sh | 28 +++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 docker/rootfs/etc/cont-init.d/01_envfile.sh diff --git a/docker/rootfs/etc/cont-init.d/.gitignore b/docker/rootfs/etc/cont-init.d/.gitignore index d6b7ef32c..06b88e3d5 100644 --- a/docker/rootfs/etc/cont-init.d/.gitignore +++ b/docker/rootfs/etc/cont-init.d/.gitignore @@ -1,2 +1,3 @@ * !.gitignore +!01_envfile.sh diff --git a/docker/rootfs/etc/cont-init.d/01_envfile.sh b/docker/rootfs/etc/cont-init.d/01_envfile.sh new file mode 100644 index 000000000..be87c49a8 --- /dev/null +++ b/docker/rootfs/etc/cont-init.d/01_envfile.sh @@ -0,0 +1,28 @@ +#! /bin/bash +# ref: https://github.com/linuxserver/docker-baseimage-alpine/blob/master/root/etc/cont-init.d/01-envfile + +# in s6, environmental variables are written as text files for s6 to monitor +for FILENAME in $(find /var/run/s6/container_environment/ | grep "^.*__FILE"); do + echo "[secret-init] Evaluating ${FILENAME}" + + # set SECRETFILE to the contents of the variable + SECRETFILE=$(cat ${FILENAME}) + # SECRETFILE=${FILENAME} + echo "[secret-init] Setting SECRETFILE to ${SECRETFILE}..." # DEBUG - rm for prod! + + # if SECRETFILE exists / is not null + if [[ -f ${SECRETFILE} ]]; then + # strip the appended "__FILE" from environmental variable name ... + STRIPFILE=$(echo $FILENAME | sed "s/__FILE//g") + echo "[secret-init] Set STRIPFILE to ${STRIPFILE}" # DEBUG - rm for prod! + + # ... and set value to contents of secretfile + # since s6 uses text files, this is effectively "export ..." + cat ${SECRETFILE} > ${STRIPFILE} + echo "[secret-init] Set ${STRIPFILE} to $(cat ${STRIPFILE})" # DEBUG - rm for prod!" + echo "[secret-init] Success! ${STRIPFILE##*/} set from ${FILENAME##*/}" + + else + echo "[secret-init] cannot find secret in ${FILENAME##*/}" + fi +done From 15c4857a4bb256a238944a3d4ce76671b825ce9a Mon Sep 17 00:00:00 2001 From: ahgraber Date: Thu, 4 Feb 2021 14:03:17 -0500 Subject: [PATCH 06/16] fix /docker/dev/docker-compose.yaml --- docker/dev/docker-compose.yaml | 78 +++++++++++++++++----------------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/docker/dev/docker-compose.yaml b/docker/dev/docker-compose.yaml index 46322a48d..1cc1bfaa8 100644 --- a/docker/dev/docker-compose.yaml +++ b/docker/dev/docker-compose.yaml @@ -2,48 +2,50 @@ # RUN: ```docker-compose --env-file``` version: "3.7" -secrets: - # paths assume the .secrets folder is sibling depth to folder where docker-compose resides - DB_ROOT_PWD: - file: ../../../homelab-setup/docker_secrets/db_root_pwd.txt - MYSQL_PWD: - file: ../../../homelab-setup/docker_secrets/mysql_pwd.txt +# secrets: +# # paths assume the .secrets folder is sibling depth to folder where docker-compose resides +# DB_ROOT_PWD: +# file: ../../../homelab-setup/docker_secrets/db_root_pwd.txt +# MYSQL_PWD: +# file: ../../../homelab-setup/docker_secrets/mysql_pwd.txt services: - mariadb: - image: mariadb:test - container_name: mariadb - secrets: - - DB_ROOT_PWD - - MYSQL_PWD - environment: - # MYSQL_ROOT_PASSWORD: "npm" - MYSQL_ROOT_PASSWORD__FILE: /run/secrets/DB_ROOT_PWD - MYSQL_DATABASE: "npm" - MYSQL_USER: "npm" - # MYSQL_PASSWORD: "npm" - MYSQL_PASSWORD__FILE: /run/secrets/MYSQL_PWD - volumes: - - ./data/mysql:/var/lib/mysql + # mariadb: + # image: mariadb:test + # container_name: mariadb + # secrets: + # - DB_ROOT_PWD + # - MYSQL_PWD + # environment: + # # MYSQL_ROOT_PASSWORD: "npm" + # MYSQL_ROOT_PASSWORD__FILE: /run/secrets/DB_ROOT_PWD + # MYSQL_DATABASE: "npm" + # MYSQL_USER: "npm" + # # MYSQL_PASSWORD: "npm" + # MYSQL_PASSWORD__FILE: /run/secrets/MYSQL_PWD + # volumes: + # - ./data/mysql:/var/lib/mysql npm: build: - context: ../../ - dockerfile: ./docker/Dockerfile + context: ../ + dockerfile: ./dev/Dockerfile + # args: + # TARGETPLATFORM: arm64v8 image: npm:test # provide a name and tag for the image container_name: npm - secrets: - - MYSQL_PWD - environment: - DISABLE_IPV6: 'true' - DB_MYSQL_HOST: "mariadb" - DB_MYSQL_PORT: 3306 - MYSQL_DATABASE: "npm" - MYSQL_USER: "npm" - # MYSQL_PASSWORD: "npm" - MYSQL_PASSWORD__FILE: /run/secrets/MYSQL_PWD - volumes: - - ./data/npm:/data - - ./data/letsencrypt:/etc/letsencrypt - depends_on: - - mariadb \ No newline at end of file + # secrets: + # - MYSQL_PWD + # environment: + # DISABLE_IPV6: 'true' + # DB_MYSQL_HOST: "mariadb" + # DB_MYSQL_PORT: 3306 + # MYSQL_DATABASE: "npm" + # MYSQL_USER: "npm" + # # MYSQL_PASSWORD: "npm" + # MYSQL_PASSWORD__FILE: /run/secrets/MYSQL_PWD + # volumes: + # - ./data/npm:/data + # - ./data/letsencrypt:/etc/letsencrypt + # depends_on: + # - mariadb \ No newline at end of file From ef3a073af5bc6532899de99d3053195f5a1d3409 Mon Sep 17 00:00:00 2001 From: Alex Graber Date: Fri, 5 Feb 2021 16:52:24 -0500 Subject: [PATCH 07/16] local builds & secrets --- Dockerfile | 39 ++++++++++ docker-compose.yaml | 86 +++++++++++++++++++++ docker/Dockerfile | 2 +- docker/dev/Dockerfile | 28 +++---- docker/dev/docker-compose.yaml | 4 +- docker/rootfs/etc/cont-init.d/01_envfile.sh | 17 ++-- 6 files changed, 152 insertions(+), 24 deletions(-) create mode 100644 Dockerfile create mode 100644 docker-compose.yaml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..f40029772 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,39 @@ +FROM jc21/alpine-nginx-full:node +LABEL maintainer="Jamie Curnow " + +ENV SUPPRESS_NO_CONFIG_WARNING=1 +ENV S6_FIX_ATTRS_HIDDEN=1 +ENV NODE_ENV=production + +RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \ + && apk update \ + && apk add python3 certbot jq \ + && python3 -m ensurepip \ + && rm -rf /var/cache/apk/* + +# s6 overlay +COPY scripts/install-s6 /tmp/install-s6 +RUN /tmp/install-s6 "${TARGETPLATFORM}" && rm -f /tmp/install-s6 + +EXPOSE 80 +EXPOSE 81 +EXPOSE 443 + +ADD backend /app +ADD frontend/dist /app/frontend +COPY global /app/global + +WORKDIR /app +RUN yarn install + +# add late to limit cache-busting by modifications +COPY docker/rootfs / + +# Remove frontend service not required for prod, dev nginx config as well +RUN rm -rf /etc/services.d/frontend +RUN rm -f /etc/nginx/conf.d/dev.conf + +VOLUME [ "/data", "/etc/letsencrypt" ] +ENTRYPOINT [ "/init" ] + +HEALTHCHECK --interval=5s --timeout=3s CMD /bin/check-health \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 000000000..60de491fc --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,86 @@ +# docker-compose.yml +version: "3.7" + +secrets: + # paths assume the .secrets folder is sibling depth to folder where docker-compose resides + DB_ROOT_PWD: + file: ../.secrets/db_root_pwd.txt + MYSQL_PWD: + file: ../.secrets/mysql_pwd.txt + +# networks: +# npm-bridge: # arbitrary name +# driver: bridge + +services: + mariadb: + image: ahgraber/mariadb-aria:test + container_name: mariadb + secrets: + - DB_ROOT_PWD + - MYSQL_PWD + # networks: + # - npm-bridge + # ports: + # - 3306:3306 + environment: + # MYSQL_ROOT_PASSWORD: "npm" + MYSQL_ROOT_PASSWORD__FILE: /run/secrets/DB_ROOT_PWD + MYSQL_DATABASE: "npm" + MYSQL_USER: "npm" + # MYSQL_PASSWORD: "npm" + MYSQL_PASSWORD__FILE: /run/secrets/MYSQL_PWD + volumes: + - ./data/mysql:/var/lib/mysql + # - npm-mount:/var/lib/mysql + # restart: unless-stopped + + npm: + build: + context: . + dockerfile: ./Dockerfile + # args: + # TARGETPLATFORM: arm64v8 + image: nginx-proxy-manager:test # provide a name and tag for the image + container_name: npm + secrets: + - MYSQL_PWD + # networks: + # - npm-bridge + ports: + - 8080:80 + - 8443:443 + - 8888:81 + environment: + DISABLE_IPV6: 'true' + DB_MYSQL_HOST: "mariadb" + DB_MYSQL_PORT: 3306 + DB_MYSQL_NAME: "npm" + DB_MYSQL_USER: "npm" + # DB_MYSQL_PASSWORD: "npm" + DB_MYSQL_PASSWORD__FILE: /run/secrets/MYSQL_PWD + # DB_MYSQL_PASSWORD: "sqlL3tm3in" + volumes: + - ./data/npm:/data + - ./data/letsencrypt:/etc/letsencrypt + # - npm-mount:/data + # - letsencrypt-mount:/etc/letsencrypt + depends_on: + - mariadb + # restart: unless-stopped + +# ## https://stackoverflow.com/questions/45282608/how-to-directly-mount-nfs-share-volume-in-container-using-docker-compose-v3 +# volumes: +# npm-mount: +# driver: local +# driver_opts: +# type: nfs +# o: nfsvers=4,addr=10.2.1.1,rw,retry=1,soft,nolock +# device: ":/npm" +# letsencrypt-mount: +# driver: local +# driver_opts: +# type: nfs +# o: nfsvers=4,addr=10.2.1.1,rw,retry=1,soft,nolock +# device: ":/letsencrypt" + \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile index f243208c4..66bbd92c4 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -43,6 +43,6 @@ RUN yarn install RUN rm -rf /etc/services.d/frontend RUN rm -f /etc/nginx/conf.d/dev.conf VOLUME [ "/data", "/etc/letsencrypt" ] -CMD [ "/init" ] +ENTRYPOINT [ "/init" ] HEALTHCHECK --interval=5s --timeout=3s CMD /bin/check-health diff --git a/docker/dev/Dockerfile b/docker/dev/Dockerfile index 9ac2911f1..cad4bc7b6 100644 --- a/docker/dev/Dockerfile +++ b/docker/dev/Dockerfile @@ -1,9 +1,9 @@ FROM jc21/alpine-nginx-full:node LABEL maintainer="Jamie Curnow " -ENV S6_LOGGING=0 ENV SUPPRESS_NO_CONFIG_WARNING=1 ENV S6_FIX_ATTRS_HIDDEN=1 +ENV NODE_ENV=production RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \ && apk update \ @@ -11,22 +11,24 @@ RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \ && python3 -m ensurepip \ && rm -rf /var/cache/apk/* -# Task -RUN cd /usr \ - && curl -sL https://taskfile.dev/install.sh | sh \ - && cd /root - -COPY rootfs / -RUN rm -f /etc/nginx/conf.d/production.conf - # s6 overlay -RUN curl -L -o /tmp/s6-overlay-amd64.tar.gz "https://github.com/just-containers/s6-overlay/releases/download/v1.22.1.0/s6-overlay-amd64.tar.gz" \ - && tar -xzf /tmp/s6-overlay-amd64.tar.gz -C / +COPY scripts/install-s6 /tmp/install-s6 +RUN /tmp/install-s6 "${TARGETPLATFORM}" && rm -f /tmp/install-s6 EXPOSE 80 EXPOSE 81 EXPOSE 443 -ENTRYPOINT [ "/init" ] +COPY docker/rootfs / +ADD backend /app +ADD frontend/dist /app/frontend +COPY global /app/global + +WORKDIR /app +RUN yarn install + +# Remove frontend service not required for prod, dev nginx config as well +RUN rm -rf /etc/services.d/frontend RUN rm -f /etc/nginx/conf.d/dev.conf -HEALTHCHECK --interval=5s --timeout=3s CMD /bin/check-health +VOLUME [ "/data", "/etc/letsencrypt" ] +ENTRYPOINT [ "/init" ] \ No newline at end of file diff --git a/docker/dev/docker-compose.yaml b/docker/dev/docker-compose.yaml index 1cc1bfaa8..35871966d 100644 --- a/docker/dev/docker-compose.yaml +++ b/docker/dev/docker-compose.yaml @@ -28,8 +28,8 @@ services: npm: build: - context: ../ - dockerfile: ./dev/Dockerfile + context: ../../ + dockerfile: ./docker/dev/Dockerfile # args: # TARGETPLATFORM: arm64v8 image: npm:test # provide a name and tag for the image diff --git a/docker/rootfs/etc/cont-init.d/01_envfile.sh b/docker/rootfs/etc/cont-init.d/01_envfile.sh index be87c49a8..a0bc5ace1 100644 --- a/docker/rootfs/etc/cont-init.d/01_envfile.sh +++ b/docker/rootfs/etc/cont-init.d/01_envfile.sh @@ -2,10 +2,11 @@ # ref: https://github.com/linuxserver/docker-baseimage-alpine/blob/master/root/etc/cont-init.d/01-envfile # in s6, environmental variables are written as text files for s6 to monitor -for FILENAME in $(find /var/run/s6/container_environment/ | grep "^.*__FILE"); do - echo "[secret-init] Evaluating ${FILENAME}" +# seach through full-path filenames for files ending in "__FILE" +for FILENAME in $(find /var/run/s6/container_environment/ | grep "__FILE$"); do + echo "[secret-init] Evaluating ${FILENAME##*/}" - # set SECRETFILE to the contents of the variable + # set SECRETFILE to the contents of the full-path textfile SECRETFILE=$(cat ${FILENAME}) # SECRETFILE=${FILENAME} echo "[secret-init] Setting SECRETFILE to ${SECRETFILE}..." # DEBUG - rm for prod! @@ -13,16 +14,16 @@ for FILENAME in $(find /var/run/s6/container_environment/ | grep "^.*__FILE"); d # if SECRETFILE exists / is not null if [[ -f ${SECRETFILE} ]]; then # strip the appended "__FILE" from environmental variable name ... - STRIPFILE=$(echo $FILENAME | sed "s/__FILE//g") + STRIPFILE=$(echo ${FILENAME} | sed "s/__FILE//g") echo "[secret-init] Set STRIPFILE to ${STRIPFILE}" # DEBUG - rm for prod! - + # ... and set value to contents of secretfile # since s6 uses text files, this is effectively "export ..." - cat ${SECRETFILE} > ${STRIPFILE} + cat $(${SECRETFILE} | xargs) > ${STRIPFILE} echo "[secret-init] Set ${STRIPFILE} to $(cat ${STRIPFILE})" # DEBUG - rm for prod!" - echo "[secret-init] Success! ${STRIPFILE##*/} set from ${FILENAME##*/}" + echo "[secret-init] Success! ${STRIPFILE} set from ${FILENAME}" else - echo "[secret-init] cannot find secret in ${FILENAME##*/}" + echo "[secret-init] cannot find secret in ${FILENAME}" fi done From 9c3a7b02ec644f17f83f3881401c01c39fdef944 Mon Sep 17 00:00:00 2001 From: Loris Bergeron Date: Fri, 5 Feb 2021 23:17:51 +0100 Subject: [PATCH 08/16] Add EuroDNS as a DNS provider --- global/certbot-dns-plugins.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/global/certbot-dns-plugins.js b/global/certbot-dns-plugins.js index 7bea831fa..0be2f7794 100644 --- a/global/certbot-dns-plugins.js +++ b/global/certbot-dns-plugins.js @@ -291,4 +291,14 @@ aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY`, credentials: 'certbot_dns_vultr:dns_vultr_key = YOUR_VULTR_API_KEY', full_plugin_name: 'certbot-dns-vultr:dns-vultr', }, + eurodns: { + display_name: 'EuroDNS', + package_name: 'certbot-dns-eurodns', + package_version: '0.0.4', + dependencies: '', + credentials: `dns_eurodns_applicationId = myuser +dns_eurodns_apiKey = mysecretpassword +dns_eurodns_endpoint = https://rest-api.eurodns.com/user-api-gateway/proxy`, + full_plugin_name: 'certbot-dns-eurodns:dns-eurodns', + }, }; From d29650882b245a62cf2a0e1cd26021c592f1aff5 Mon Sep 17 00:00:00 2001 From: Loris Bergeron Date: Fri, 5 Feb 2021 23:29:01 +0100 Subject: [PATCH 09/16] EuroDNS fix key-spacing error --- global/certbot-dns-plugins.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/global/certbot-dns-plugins.js b/global/certbot-dns-plugins.js index 0be2f7794..8426ef400 100644 --- a/global/certbot-dns-plugins.js +++ b/global/certbot-dns-plugins.js @@ -292,11 +292,11 @@ aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY`, full_plugin_name: 'certbot-dns-vultr:dns-vultr', }, eurodns: { - display_name: 'EuroDNS', - package_name: 'certbot-dns-eurodns', - package_version: '0.0.4', - dependencies: '', - credentials: `dns_eurodns_applicationId = myuser + display_name: 'EuroDNS', + package_name: 'certbot-dns-eurodns', + package_version: '0.0.4', + dependencies: '', + credentials: `dns_eurodns_applicationId = myuser dns_eurodns_apiKey = mysecretpassword dns_eurodns_endpoint = https://rest-api.eurodns.com/user-api-gateway/proxy`, full_plugin_name: 'certbot-dns-eurodns:dns-eurodns', From 0ca5587a6f7fa9f0a4a2cb8d302d0c42225561a6 Mon Sep 17 00:00:00 2001 From: Loris Bergeron Date: Fri, 5 Feb 2021 23:40:31 +0100 Subject: [PATCH 10/16] EuroDNS fix key-spacing error --- global/certbot-dns-plugins.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/global/certbot-dns-plugins.js b/global/certbot-dns-plugins.js index 8426ef400..b055f848a 100644 --- a/global/certbot-dns-plugins.js +++ b/global/certbot-dns-plugins.js @@ -291,12 +291,13 @@ aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY`, credentials: 'certbot_dns_vultr:dns_vultr_key = YOUR_VULTR_API_KEY', full_plugin_name: 'certbot-dns-vultr:dns-vultr', }, + //####################################################// eurodns: { - display_name: 'EuroDNS', - package_name: 'certbot-dns-eurodns', - package_version: '0.0.4', - dependencies: '', - credentials: `dns_eurodns_applicationId = myuser + display_name: 'EuroDNS', + package_name: 'certbot-dns-eurodns', + package_version: '0.0.4', + dependencies: '', + credentials: `dns_eurodns_applicationId = myuser dns_eurodns_apiKey = mysecretpassword dns_eurodns_endpoint = https://rest-api.eurodns.com/user-api-gateway/proxy`, full_plugin_name: 'certbot-dns-eurodns:dns-eurodns', From 5abb9458c730784cbfa9c6c58e0600a8f71af4ec Mon Sep 17 00:00:00 2001 From: ahgraber Date: Fri, 5 Feb 2021 23:47:30 -0500 Subject: [PATCH 11/16] fix linebreaks in secrets --- Dockerfile | 2 +- docker/rootfs/etc/cont-init.d/01_envfile.sh | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index f40029772..b49e1fc22 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM jc21/alpine-nginx-full:node +FROM --platform=${TARGETPLATFORM:-linux/amd64} jc21/alpine-nginx-full:node LABEL maintainer="Jamie Curnow " ENV SUPPRESS_NO_CONFIG_WARNING=1 diff --git a/docker/rootfs/etc/cont-init.d/01_envfile.sh b/docker/rootfs/etc/cont-init.d/01_envfile.sh index a0bc5ace1..90dee690a 100644 --- a/docker/rootfs/etc/cont-init.d/01_envfile.sh +++ b/docker/rootfs/etc/cont-init.d/01_envfile.sh @@ -19,7 +19,8 @@ for FILENAME in $(find /var/run/s6/container_environment/ | grep "__FILE$"); do # ... and set value to contents of secretfile # since s6 uses text files, this is effectively "export ..." - cat $(${SECRETFILE} | xargs) > ${STRIPFILE} + # cat ${SECRETFILE} > ${STRIPFILE} + cat $(${SECRETFILE} | sed "s/[^\w.-]+//g") > ${STRIPFILE} echo "[secret-init] Set ${STRIPFILE} to $(cat ${STRIPFILE})" # DEBUG - rm for prod!" echo "[secret-init] Success! ${STRIPFILE} set from ${FILENAME}" From 7fcc4a7ef07fcf8180da906e4c722f9d93a5a754 Mon Sep 17 00:00:00 2001 From: ahgraber Date: Sat, 6 Feb 2021 20:05:40 -0500 Subject: [PATCH 12/16] cleanup --- Dockerfile | 39 --------- docker-compose.yaml | 86 ------------------- docker/Dockerfile | 5 +- docker/rootfs/etc/cont-init.d/.gitignore | 2 +- .../{01_envfile.sh => 01_s6-secret-init.sh} | 15 ++-- 5 files changed, 12 insertions(+), 135 deletions(-) delete mode 100644 Dockerfile delete mode 100644 docker-compose.yaml rename docker/rootfs/etc/cont-init.d/{01_envfile.sh => 01_s6-secret-init.sh} (64%) diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index b49e1fc22..000000000 --- a/Dockerfile +++ /dev/null @@ -1,39 +0,0 @@ -FROM --platform=${TARGETPLATFORM:-linux/amd64} jc21/alpine-nginx-full:node -LABEL maintainer="Jamie Curnow " - -ENV SUPPRESS_NO_CONFIG_WARNING=1 -ENV S6_FIX_ATTRS_HIDDEN=1 -ENV NODE_ENV=production - -RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \ - && apk update \ - && apk add python3 certbot jq \ - && python3 -m ensurepip \ - && rm -rf /var/cache/apk/* - -# s6 overlay -COPY scripts/install-s6 /tmp/install-s6 -RUN /tmp/install-s6 "${TARGETPLATFORM}" && rm -f /tmp/install-s6 - -EXPOSE 80 -EXPOSE 81 -EXPOSE 443 - -ADD backend /app -ADD frontend/dist /app/frontend -COPY global /app/global - -WORKDIR /app -RUN yarn install - -# add late to limit cache-busting by modifications -COPY docker/rootfs / - -# Remove frontend service not required for prod, dev nginx config as well -RUN rm -rf /etc/services.d/frontend -RUN rm -f /etc/nginx/conf.d/dev.conf - -VOLUME [ "/data", "/etc/letsencrypt" ] -ENTRYPOINT [ "/init" ] - -HEALTHCHECK --interval=5s --timeout=3s CMD /bin/check-health \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml deleted file mode 100644 index 60de491fc..000000000 --- a/docker-compose.yaml +++ /dev/null @@ -1,86 +0,0 @@ -# docker-compose.yml -version: "3.7" - -secrets: - # paths assume the .secrets folder is sibling depth to folder where docker-compose resides - DB_ROOT_PWD: - file: ../.secrets/db_root_pwd.txt - MYSQL_PWD: - file: ../.secrets/mysql_pwd.txt - -# networks: -# npm-bridge: # arbitrary name -# driver: bridge - -services: - mariadb: - image: ahgraber/mariadb-aria:test - container_name: mariadb - secrets: - - DB_ROOT_PWD - - MYSQL_PWD - # networks: - # - npm-bridge - # ports: - # - 3306:3306 - environment: - # MYSQL_ROOT_PASSWORD: "npm" - MYSQL_ROOT_PASSWORD__FILE: /run/secrets/DB_ROOT_PWD - MYSQL_DATABASE: "npm" - MYSQL_USER: "npm" - # MYSQL_PASSWORD: "npm" - MYSQL_PASSWORD__FILE: /run/secrets/MYSQL_PWD - volumes: - - ./data/mysql:/var/lib/mysql - # - npm-mount:/var/lib/mysql - # restart: unless-stopped - - npm: - build: - context: . - dockerfile: ./Dockerfile - # args: - # TARGETPLATFORM: arm64v8 - image: nginx-proxy-manager:test # provide a name and tag for the image - container_name: npm - secrets: - - MYSQL_PWD - # networks: - # - npm-bridge - ports: - - 8080:80 - - 8443:443 - - 8888:81 - environment: - DISABLE_IPV6: 'true' - DB_MYSQL_HOST: "mariadb" - DB_MYSQL_PORT: 3306 - DB_MYSQL_NAME: "npm" - DB_MYSQL_USER: "npm" - # DB_MYSQL_PASSWORD: "npm" - DB_MYSQL_PASSWORD__FILE: /run/secrets/MYSQL_PWD - # DB_MYSQL_PASSWORD: "sqlL3tm3in" - volumes: - - ./data/npm:/data - - ./data/letsencrypt:/etc/letsencrypt - # - npm-mount:/data - # - letsencrypt-mount:/etc/letsencrypt - depends_on: - - mariadb - # restart: unless-stopped - -# ## https://stackoverflow.com/questions/45282608/how-to-directly-mount-nfs-share-volume-in-container-using-docker-compose-v3 -# volumes: -# npm-mount: -# driver: local -# driver_opts: -# type: nfs -# o: nfsvers=4,addr=10.2.1.1,rw,retry=1,soft,nolock -# device: ":/npm" -# letsencrypt-mount: -# driver: local -# driver_opts: -# type: nfs -# o: nfsvers=4,addr=10.2.1.1,rw,retry=1,soft,nolock -# device: ":/letsencrypt" - \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile index 66bbd92c4..34ee5c449 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -13,6 +13,7 @@ ARG BUILD_DATE ENV SUPPRESS_NO_CONFIG_WARNING=1 ENV S6_FIX_ATTRS_HIDDEN=1 +ENV S6_BEHAVIOUR_IF_STAGE2_FAILS=1 ENV NODE_ENV=production RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \ @@ -31,7 +32,6 @@ EXPOSE 80 EXPOSE 81 EXPOSE 443 -COPY docker/rootfs / ADD backend /app ADD frontend/dist /app/frontend COPY global /app/global @@ -39,6 +39,9 @@ COPY global /app/global WORKDIR /app RUN yarn install +# add late to limit cache-busting by modifications +COPY docker/rootfs / + # Remove frontend service not required for prod, dev nginx config as well RUN rm -rf /etc/services.d/frontend RUN rm -f /etc/nginx/conf.d/dev.conf diff --git a/docker/rootfs/etc/cont-init.d/.gitignore b/docker/rootfs/etc/cont-init.d/.gitignore index 06b88e3d5..f04f0f6e0 100644 --- a/docker/rootfs/etc/cont-init.d/.gitignore +++ b/docker/rootfs/etc/cont-init.d/.gitignore @@ -1,3 +1,3 @@ * !.gitignore -!01_envfile.sh +!*.sh diff --git a/docker/rootfs/etc/cont-init.d/01_envfile.sh b/docker/rootfs/etc/cont-init.d/01_s6-secret-init.sh similarity index 64% rename from docker/rootfs/etc/cont-init.d/01_envfile.sh rename to docker/rootfs/etc/cont-init.d/01_s6-secret-init.sh index 90dee690a..f145807ab 100644 --- a/docker/rootfs/etc/cont-init.d/01_envfile.sh +++ b/docker/rootfs/etc/cont-init.d/01_s6-secret-init.sh @@ -1,28 +1,27 @@ -#! /bin/bash +#!/usr/bin/with-contenv bash # ref: https://github.com/linuxserver/docker-baseimage-alpine/blob/master/root/etc/cont-init.d/01-envfile # in s6, environmental variables are written as text files for s6 to monitor # seach through full-path filenames for files ending in "__FILE" for FILENAME in $(find /var/run/s6/container_environment/ | grep "__FILE$"); do - echo "[secret-init] Evaluating ${FILENAME##*/}" + echo "[secret-init] Evaluating ${FILENAME##*/} ..." # set SECRETFILE to the contents of the full-path textfile SECRETFILE=$(cat ${FILENAME}) # SECRETFILE=${FILENAME} - echo "[secret-init] Setting SECRETFILE to ${SECRETFILE}..." # DEBUG - rm for prod! + # echo "[secret-init] Set SECRETFILE to ${SECRETFILE}" # DEBUG - rm for prod! # if SECRETFILE exists / is not null if [[ -f ${SECRETFILE} ]]; then # strip the appended "__FILE" from environmental variable name ... STRIPFILE=$(echo ${FILENAME} | sed "s/__FILE//g") - echo "[secret-init] Set STRIPFILE to ${STRIPFILE}" # DEBUG - rm for prod! + # echo "[secret-init] Set STRIPFILE to ${STRIPFILE}" # DEBUG - rm for prod! # ... and set value to contents of secretfile # since s6 uses text files, this is effectively "export ..." - # cat ${SECRETFILE} > ${STRIPFILE} - cat $(${SECRETFILE} | sed "s/[^\w.-]+//g") > ${STRIPFILE} - echo "[secret-init] Set ${STRIPFILE} to $(cat ${STRIPFILE})" # DEBUG - rm for prod!" - echo "[secret-init] Success! ${STRIPFILE} set from ${FILENAME}" + printf $(cat ${SECRETFILE}) > ${STRIPFILE} + # echo "[secret-init] Set ${STRIPFILE##*/} to $(cat ${STRIPFILE})" # DEBUG - rm for prod!" + echo "[secret-init] Success! ${STRIPFILE##*/} set from ${FILENAME##*/}" else echo "[secret-init] cannot find secret in ${FILENAME}" From 11175aaa5f8122ef8a5bec13bd2f2becfd0d087f Mon Sep 17 00:00:00 2001 From: ahgraber Date: Sun, 7 Feb 2021 09:52:37 -0500 Subject: [PATCH 13/16] revert docker/dev before PR --- docker/dev/Dockerfile | 28 +++++++++---------- docker/dev/docker-compose.yaml | 51 ---------------------------------- 2 files changed, 13 insertions(+), 66 deletions(-) delete mode 100644 docker/dev/docker-compose.yaml diff --git a/docker/dev/Dockerfile b/docker/dev/Dockerfile index cad4bc7b6..1e4bdad85 100644 --- a/docker/dev/Dockerfile +++ b/docker/dev/Dockerfile @@ -1,9 +1,9 @@ FROM jc21/alpine-nginx-full:node LABEL maintainer="Jamie Curnow " +ENV S6_LOGGING=0 ENV SUPPRESS_NO_CONFIG_WARNING=1 ENV S6_FIX_ATTRS_HIDDEN=1 -ENV NODE_ENV=production RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \ && apk update \ @@ -11,24 +11,22 @@ RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \ && python3 -m ensurepip \ && rm -rf /var/cache/apk/* +# Task +RUN cd /usr \ + && curl -sL https://taskfile.dev/install.sh | sh \ + && cd /root + +COPY rootfs / +RUN rm -f /etc/nginx/conf.d/production.conf + # s6 overlay -COPY scripts/install-s6 /tmp/install-s6 -RUN /tmp/install-s6 "${TARGETPLATFORM}" && rm -f /tmp/install-s6 +RUN curl -L -o /tmp/s6-overlay-amd64.tar.gz "https://github.com/just-containers/s6-overlay/releases/download/v1.22.1.0/s6-overlay-amd64.tar.gz" \ + && tar -xzf /tmp/s6-overlay-amd64.tar.gz -C / EXPOSE 80 EXPOSE 81 EXPOSE 443 -COPY docker/rootfs / -ADD backend /app -ADD frontend/dist /app/frontend -COPY global /app/global - -WORKDIR /app -RUN yarn install - -# Remove frontend service not required for prod, dev nginx config as well -RUN rm -rf /etc/services.d/frontend RUN rm -f /etc/nginx/conf.d/dev.conf +ENTRYPOINT [ "/init" ] -VOLUME [ "/data", "/etc/letsencrypt" ] -ENTRYPOINT [ "/init" ] \ No newline at end of file +HEALTHCHECK --interval=5s --timeout=3s CMD /bin/check-health \ No newline at end of file diff --git a/docker/dev/docker-compose.yaml b/docker/dev/docker-compose.yaml deleted file mode 100644 index 35871966d..000000000 --- a/docker/dev/docker-compose.yaml +++ /dev/null @@ -1,51 +0,0 @@ -# docker-compose.yml -# RUN: ```docker-compose --env-file``` -version: "3.7" - -# secrets: -# # paths assume the .secrets folder is sibling depth to folder where docker-compose resides -# DB_ROOT_PWD: -# file: ../../../homelab-setup/docker_secrets/db_root_pwd.txt -# MYSQL_PWD: -# file: ../../../homelab-setup/docker_secrets/mysql_pwd.txt - -services: - # mariadb: - # image: mariadb:test - # container_name: mariadb - # secrets: - # - DB_ROOT_PWD - # - MYSQL_PWD - # environment: - # # MYSQL_ROOT_PASSWORD: "npm" - # MYSQL_ROOT_PASSWORD__FILE: /run/secrets/DB_ROOT_PWD - # MYSQL_DATABASE: "npm" - # MYSQL_USER: "npm" - # # MYSQL_PASSWORD: "npm" - # MYSQL_PASSWORD__FILE: /run/secrets/MYSQL_PWD - # volumes: - # - ./data/mysql:/var/lib/mysql - - npm: - build: - context: ../../ - dockerfile: ./docker/dev/Dockerfile - # args: - # TARGETPLATFORM: arm64v8 - image: npm:test # provide a name and tag for the image - container_name: npm - # secrets: - # - MYSQL_PWD - # environment: - # DISABLE_IPV6: 'true' - # DB_MYSQL_HOST: "mariadb" - # DB_MYSQL_PORT: 3306 - # MYSQL_DATABASE: "npm" - # MYSQL_USER: "npm" - # # MYSQL_PASSWORD: "npm" - # MYSQL_PASSWORD__FILE: /run/secrets/MYSQL_PWD - # volumes: - # - ./data/npm:/data - # - ./data/letsencrypt:/etc/letsencrypt - # depends_on: - # - mariadb \ No newline at end of file From 3964bbf3fe18ebd359bb15025c149d17b8304746 Mon Sep 17 00:00:00 2001 From: ahgraber Date: Sun, 7 Feb 2021 19:12:20 -0500 Subject: [PATCH 14/16] update advanced-config/readme with secrets --- docs/advanced-config/README.md | 61 ++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/docs/advanced-config/README.md b/docs/advanced-config/README.md index 7c622d59c..962c34221 100644 --- a/docs/advanced-config/README.md +++ b/docs/advanced-config/README.md @@ -1,5 +1,66 @@ # Advanced Configuration +## Docker Secrets + +This image supports the use of Docker secrets to import from file and keep sensitive usernames or passwords from being passed or preserved in plaintext. + +You can set any environment variable from a file by appending `__FILE` (double-underscore FILE) to the environmental variable name. + +```yml +version: "3.7" + +secrets: + # Secrets are single-line text files where the sole content is the secret + # Paths in this example assume that secrets are kept in local folder called ".secrets" + DB_ROOT_PWD: + file: .secrets/db_root_pwd.txt + MYSQL_PWD: + file: .secrets/mysql_pwd.txt + +services: + app: + image: 'jc21/nginx-proxy-manager:latest' + restart: always + ports: + # Public HTTP Port: + - '80:80' + # Public HTTPS Port: + - '443:443' + # Admin Web Port: + - '81:81' + environment: + # These are the settings to access your db + DB_MYSQL_HOST: "db" + DB_MYSQL_PORT: 3306 + DB_MYSQL_USER: "npm" + # DB_MYSQL_PASSWORD: "npm" # use secret instead + DB_MYSQL_PASSWORD__FILE: /run/secrets/MYSQL_PWD + DB_MYSQL_NAME: "npm" + # If you would rather use Sqlite uncomment this + # and remove all DB_MYSQL_* lines above + # DB_SQLITE_FILE: "/data/database.sqlite" + # Uncomment this if IPv6 is not enabled on your host + # DISABLE_IPV6: 'true' + volumes: + - ./data:/data + - ./letsencrypt:/etc/letsencrypt + depends_on: + - db + db: + image: jc21/mariadb-aria + restart: always + environment: + # MYSQL_ROOT_PASSWORD: "npm" # use secret instead + MYSQL_ROOT_PASSWORD__FILE: /run/secrets/DB_ROOT_PWD + MYSQL_DATABASE: "npm" + MYSQL_USER: "npm" + # MYSQL_PASSWORD: "npm" # use secret instead + MYSQL_PASSWORD__FILE: /run/secrets/MYSQL_PWD + volumes: + - ./data/mysql:/var/lib/mysql +``` + + ## Disabling IPv6 On some docker hosts IPv6 may not be enabled. In these cases, the following message may be seen in the log: From 272c65284726efbb4e1a93613ac2aad5d2523409 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Mon, 8 Feb 2021 11:56:21 +1000 Subject: [PATCH 15/16] Updated docs to use latest mariadb-aria image --- docs/README.md | 2 +- docs/setup/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/README.md b/docs/README.md index 3f2f62822..11c17e39c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -66,7 +66,7 @@ services: - ./data:/data - ./letsencrypt:/etc/letsencrypt db: - image: 'jc21/mariadb-aria:10.4' + image: 'jc21/mariadb-aria:latest' environment: MYSQL_ROOT_PASSWORD: 'npm' MYSQL_DATABASE: 'npm' diff --git a/docs/setup/README.md b/docs/setup/README.md index 8af7eee23..457674a59 100644 --- a/docs/setup/README.md +++ b/docs/setup/README.md @@ -51,7 +51,7 @@ services: depends_on: - db db: - image: jc21/mariadb-aria:10.4 + image: 'jc21/mariadb-aria:latest' restart: always environment: MYSQL_ROOT_PASSWORD: 'npm' From b243324c6557297602f119d625ccc9bf34139178 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Mon, 8 Feb 2021 12:23:52 +1000 Subject: [PATCH 16/16] Added contributors and bumped version --- .version | 2 +- README.md | 28 +++++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/.version b/.version index 2c9b4ef42..834f26295 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -2.7.3 +2.8.0 diff --git a/README.md b/README.md index 3e4e57244..28d4740c3 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@



- + @@ -229,6 +229,32 @@ Special thanks to the following contributors:
Kyle Harding + + + +
Alex Graber +
+ + + + + + +
MooBaloo +
+ + + + +
Shuro +
+ + + + +
Loris Bergeron +
+