diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..1360c69 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,93 @@ +# Docker +# Build and push php images to Docker Hub +name: Build and push + +on: + push: + paths: + - 'build/**' + - '.github/**' + branches: + - 'main' + tags: + - '*' + +jobs: + node20-base: + name: Build and push node20 base image + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: ./build/node20/base + file: ./build/node20/base/Dockerfile + cache-from: type=gha,scope=node20 + cache-to: type=gha,scope=node20,mode=max + push: true + tags: anzusystems/node:${{ github.ref_name }}-node20,anzusystems/node:latest-node20 + + node20-nginx: + name: Build and push node20 nginx image + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: ./build/node20/nginx + file: ./build/node20/nginx/Dockerfile + cache-from: type=gha,scope=node20-nginx + cache-to: type=gha,scope=node20-nginx,mode=max + push: true + tags: anzusystems/node:${{ github.ref_name }}-node20-nginx,anzusystems/node:latest-node20-nginx + + node20-nginx-browsers: + name: Build and push node20 nginx-browsers image + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: ./build/node20/nginx-browsers + file: ./build/node20/nginx-browsers/Dockerfile + cache-from: type=gha,scope=node20-nginx-browsers + cache-to: type=gha,scope=node20-nginx-browsers,mode=max + push: true + tags: anzusystems/node:${{ github.ref_name }}-node20-nginx-browsers,anzusystems/node:latest-node20-nginx-browsers + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..706fd07 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea +.vscode diff --git a/README.md b/README.md new file mode 100644 index 0000000..46e2e85 --- /dev/null +++ b/README.md @@ -0,0 +1,71 @@ +AnzuSystems Node Development Docker Images by Petit Press a.s. (www.sme.sk) +===== + +Main Node images from which all our Node flavors (versions with base, browsers...) are built. It is based on [official Node images](https://hub.docker.com/_/node) using our custom configuration. +Additionally, it contains [nginx](https://nginx.org/), [mariadb client](https://mariadb.com/downloads/) packages and many more. + +[[_TOC_]] + +## What's inside + +- Node 20 +- NPM +- Yarn +- AuditJS +- Supervisor +- Nginx variant: + - Nginx + - Nginx-xslt + - Nginx-geoip + - Nginx-image-filter + - Nginx-njs +- Browsers variant: + - google chrome + - firefox + - xvfb + other X libs + +See versions in [version.conf](https://github.com/anzusystems/docker-node/blob/main/versions.conf) file. + +### Special common commands + +- `env-config` - Script to run setup of config.json file using .env files and exported variables +- `fix-user` - Corrects UID and GID for container user according to host UID and GID if needed + +## Git Hooks + +Setup pre-commit hook: + +- `.git/hooks/pre-commit` + + #!/bin/bash + # + # Run update.sh script before commit + + ./update.sh + git add .github build + +## Auto-generation + +Script: + + ./update.sh + +This script has to be run before every commit. + +Script is used to autogenerate following files/folders: + +- `build/nodeXY/` - autogenerated from: + - `config/all/` + - `config/all-X.Y/` + - `config/browsers/` + - `config/browsers-X.Y/` + - `config/nginx/` + - `config/nginx-X.Y/` + - `template.Dockerfile` + - `variant-*.Dockerfile` + - `versions.conf` +- `.github/workflows/docker.yml` - autogenerated from: + - `docker.yml.template` + - `docker.yml.job.template` + +Do not change autogenerated files/folders directly. diff --git a/build/node20/base/Dockerfile b/build/node20/base/Dockerfile new file mode 100644 index 0000000..7bb4334 --- /dev/null +++ b/build/node20/base/Dockerfile @@ -0,0 +1,106 @@ +# +# NOTE: +# THIS DOCKERFILE IS GENERATED VIA "update.sh". +# PLEASE DO NOT EDIT IT DIRECTLY! +# CHECK README FOR MORE INFO. +# +FROM node:20.12.2 + +LABEL maintainer="Lubomir Stanko " + +# ---------------------------------------------------------------------------------------------------------------------- +# ENVIRONMENT VARIABLES +# ---------------------------------------------------------------------------------------------------------------------- +# Common environment variables +ENV CONFIG_OWNER_NAME=node \ + CONFIG_GROUP_NAME=node \ + CONTAINER_STOP_LOG_FILE="/var/www/html/var/log/container_stop.log" \ + COREPACK_HOME="/usr/lib/node/corepack" \ + MAIN_TERMINATED_FILE="/var/www/html/var/log/main-terminated" \ + NPM_CONFIG_LOGLEVEL=notice \ + YARN_CACHE_FOLDER="/var/cache/yarn" \ + YARN_ENABLE_TELEMETRY=0 \ + # Unset yarn version - it could break CI and we don't need it + YARN_VERSION= +# Packages +ENV RUN_DEPS="ca-certificates \ + curl \ + g++ \ + gcc \ + gettext-base \ + git \ + gnupg \ + less \ + logrotate \ + lsb-release \ + make \ + openssh-client \ + procps \ + vim \ + wget" + +# ---------------------------------------------------------------------------------------------------------------------- +# PACKAGES +# ---------------------------------------------------------------------------------------------------------------------- +RUN apt-get update && \ + apt-get install -y \ + ${RUN_DEPS} \ + supervisor=4.2.5-1 && \ +# Cleanup + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# ---------------------------------------------------------------------------------------------------------------------- +# NPM +# Install static npm version +# ---------------------------------------------------------------------------------------------------------------------- +RUN npm install --location=global npm@10.5.2 && \ + npm install --location=global auditjs@4.0.45 && \ + mkdir -p ${COREPACK_HOME} && \ + corepack prepare yarn@4.1.1 --activate && \ + corepack enable && \ +# Node cache cleanup + npm cache clean --force && \ + yarn cache clean --all +# Versions of local tools +RUN echo "node version: $(node -v) \n" \ + "npm version: $(npm -v) \n" \ + "yarn version: $(yarn -v)" + +# ---------------------------------------------------------------------------------------------------------------------- +# USER SETUP +# ---------------------------------------------------------------------------------------------------------------------- +RUN sed -i 's/^#alias l/alias l/g' /home/node/.bashrc && \ + echo "update-notifier=false" > /home/node/.npmrc && \ + mkdir -p \ + ${YARN_CACHE_FOLDER} \ + /usr/local/lib/node_modules \ + /var/run/supervisor \ + /var/www/html/var && \ + chown node:node -R \ + ${COREPACK_HOME} \ + ${YARN_CACHE_FOLDER} \ + /home/node/.npmrc \ + /usr/local/bin \ + /usr/local/lib/node_modules \ + /var/run/supervisor \ + /var/www/html + +#### +#### + +# ---------------------------------------------------------------------------------------------------------------------- +# RUN CONFIGURATION +# ---------------------------------------------------------------------------------------------------------------------- +COPY --chown=${CONFIG_OWNER_NAME}:${CONFIG_GROUP_NAME} ./etc /etc +COPY --chown=${CONFIG_OWNER_NAME}:${CONFIG_GROUP_NAME} ./usr /usr + +# ---------------------------------------------------------------------------------------------------------------------- +# RUN +# Run setup and entrypoint start +# ---------------------------------------------------------------------------------------------------------------------- +WORKDIR /var/www/html + +USER node + +ENTRYPOINT ["docker-custom-entrypoint"] diff --git a/build/node20/base/etc/supervisor/supervisord.conf b/build/node20/base/etc/supervisor/supervisord.conf new file mode 100644 index 0000000..dae2dc2 --- /dev/null +++ b/build/node20/base/etc/supervisor/supervisord.conf @@ -0,0 +1,23 @@ +[unix_http_server] +file=/var/run/supervisor/supervisor.sock +chmod=0700 +username=docker +password=docker + +[supervisorctl] +serverurl=unix:///var/run/supervisor/supervisor.sock +username=docker +password=docker + +[supervisord] +nodaemon=true +logfile=/dev/stdout +logfile_maxbytes=0 +pidfile=/var/run/supervisor/supervisord.pid +loglevel=info + +[rpcinterface:supervisor] +supervisor.rpcinterface_factory=supervisor.rpcinterface:make_main_rpcinterface + +[include] +files = /etc/supervisor/conf.d/*.conf diff --git a/build/node20/base/usr/local/bin/docker-custom-entrypoint b/build/node20/base/usr/local/bin/docker-custom-entrypoint new file mode 100755 index 0000000..5fc4448 --- /dev/null +++ b/build/node20/base/usr/local/bin/docker-custom-entrypoint @@ -0,0 +1,3 @@ +#!/bin/bash + +exec "$@" diff --git a/build/node20/base/usr/local/bin/env-config b/build/node20/base/usr/local/bin/env-config new file mode 100755 index 0000000..36ce7d3 --- /dev/null +++ b/build/node20/base/usr/local/bin/env-config @@ -0,0 +1,178 @@ +#!/bin/bash +# Script used to run the setup of config.json file using .env files and exported variables: +# - if the variable is exported in system, then it is left untouched and not exported from any env file +# - all missing variables are exported from main env file if exists +# - all missing variables (the same as from main env file) are exported from local env file if exists +set -euo pipefail + +CONFIG_DIST_PATH="public/config.json.dist" +CONFIG_PATH="public/config.json" +DEBUG=false +END_FLOW=false +ENV_APPLICATION_FILE_DIST=".env" +ENV_APPLICATION_FILE=".env.local" +PROJECT_ROOT="/var/www/html" + +function echo_debug() { + if ${DEBUG}; then + echo -e "[DEBUG] $*" + fi +} + +function help() { + echo -en "$(printf %q "${BASH_SOURCE[0]}") [OPERATION] [OPTIONS]... + \nBash script to run env config.json setup inside the container. + + Options: + --config-dist-path (Optional) Path to existing config.json.dist file + Default: public/config.json.dist + --config-path (Optional) Path to config.json file to be created + Default: public/config.json + --debug (Optional) Whether to enable debug output + --env-dist-file (Optional) Path to existing .env file + Default: .env + --env-file (Optional) Path to .env.local file to be created + Default: .env.local + --project-root (Optional) Path project home where to find config and env files + Default: /var/www/html + + Examples: + $(printf %q "${BASH_SOURCE[0]}") --config-dist-path dist/config.json.dist --config-path dist/config.json + \n" +} + +function set_vars_from_file() { + if [ -z "${MISSING_VARIABLE_NAMES}" ]; then + echo_debug "All variables are exported, there is nothing needed to export from $1 file" + return + fi + while IFS= read -r variable; do + variable_name=$(echo "${variable?}" | cut -d "=" -f 1) + # Export only missing variables + if echo "${MISSING_VARIABLE_NAMES}" | grep -q -w "$variable_name"; then + ENV_VARS+=("${variable?}") + echo_debug "Preparing variable for export: ${variable?}" + fi + done < <(grep -v "^#" "$1" | sed '/^$/d') + echo_debug "Prepared variables for export:" "${ENV_VARS[@]}" + for var in "${ENV_VARS[@]}"; do export "${var?}"; done +} + +while [ "$#" -gt 0 ]; do + case $1 in + --config-path) + CONFIG_PATH=$2 + shift 2 + continue + ;; + --config-path=*) + CONFIG_PATH=${1#*=} + shift 1 + continue + ;; + --config-dist-path) + CONFIG_DIST_PATH=$2 + shift 2 + continue + ;; + --config-dist-path=*) + CONFIG_DIST_PATH=${1#*=} + shift 1 + continue + ;; + --debug) + DEBUG=true + shift 1 + continue + ;; + --env-file) + ENV_APPLICATION_FILE=$2 + shift 2 + continue + ;; + --env-file=*) + ENV_APPLICATION_FILE=${1#*=} + shift 1 + continue + ;; + --env-dist-file) + ENV_APPLICATION_FILE_DIST=$2 + shift 2 + continue + ;; + --env-dist-file=*) + ENV_APPLICATION_FILE_DIST=${1#*=} + shift 1 + continue + ;; + --project-root) + PROJECT_ROOT=$2 + shift 2 + continue + ;; + --project-root=*) + PROJECT_ROOT=${1#*=} + shift 1 + continue + ;; + -h | --help) + help + exit + ;; + *) + echo "[WARN] Unknown command line switch: $1" >&2 + help + exit + ;; + esac +done + +if [ ! -f "${CONFIG_DIST_PATH}" ]; then + echo "[INFO] Project root: ${PROJECT_ROOT}" + echo "[ERROR] File ${CONFIG_DIST_PATH} does not exist, provide correct path using --config-dist-path option" + END_FLOW=true +fi + +if [ ! -f "${ENV_APPLICATION_FILE_DIST}" ]; then + echo "[INFO] Project root: ${PROJECT_ROOT}" + echo "[ERROR] File ${ENV_APPLICATION_FILE_DIST} does not exist, provide correct path using --env-dist-path option" + END_FLOW=true +fi + +if ${END_FLOW}; then + echo -e "\n" + help + exit 1 +fi + +cd "${PROJECT_ROOT}" || exit 1 + +# Get all variables from main env file +ALL_VARIABLE_NAMES=$(grep -v "^#" "${ENV_APPLICATION_FILE_DIST}" | sed -e '/^$/d' | cut -d "=" -f 1) +# Set missing variables to all variables, we will filter them later in the script +MISSING_VARIABLE_NAMES=${ALL_VARIABLE_NAMES} + +# Remove already exported (existing) variables from missing variables to make the list clear +for variable_name in ${ALL_VARIABLE_NAMES}; do + if [ -n "${!variable_name+x}" ]; then + MISSING_VARIABLE_NAMES=${MISSING_VARIABLE_NAMES//$variable_name/} + echo_debug "Variable already exported: ${variable_name?}" + fi +done +MISSING_VARIABLE_NAMES="$(echo "${MISSING_VARIABLE_NAMES}" | sed '/^$/d' | tr '\n' ' ')" +echo_debug "Variables to read from env files: ${MISSING_VARIABLE_NAMES}" + +# Export missing variables from main env file if exists +if [ -f "${ENV_APPLICATION_FILE_DIST}" ]; then + echo_debug "Reading variables from ${ENV_APPLICATION_FILE_DIST} file" + set_vars_from_file "${ENV_APPLICATION_FILE_DIST}" +fi + +# Export missing variables from local env file if exists +if [ -f "${ENV_APPLICATION_FILE}" ]; then + echo_debug "Reading variables from ${ENV_APPLICATION_FILE} file" + set_vars_from_file "${ENV_APPLICATION_FILE}" +fi + +mkdir -p "$(dirname "${CONFIG_PATH}")" +envsubst <"${CONFIG_DIST_PATH}" >"${CONFIG_PATH}" diff --git a/build/node20/base/usr/local/bin/fix-user b/build/node20/base/usr/local/bin/fix-user new file mode 100755 index 0000000..fafd384 --- /dev/null +++ b/build/node20/base/usr/local/bin/fix-user @@ -0,0 +1,97 @@ +#!/bin/bash +# +# Corrects UID and GID for container user according to host UID and GID if needed +# +# #1 Example: UID 1000 and GID 1000: +# ./fix-user test user 1000 1000 +# - 'user' has UID 1000 and GID 1000 by default - nothing will happen +# +# #2 Example: UID 501 (not exists in docker image) and GID 501 (not exists in docker image): +# ./fix-user test user 501 501 +# - 'user' UID will change to 501 +# - 'user' GID will change to 501 +# +# #3 Example: UID 35 (exists in docker image as 'games') and GID 100 (exists in docker image as 'users'): +# ./fix-user test user 35 100 +# - 'games' UID will change to random free UID (1100-2000) to release UID 35 +# - 'user' UID will change to 35 +# - adds user 'user' to group 'users' + +HOST_USER=$1 +CONTAINER_USER_NAME=$2 +HOST_USER_ID=$3 +HOST_GROUP_ID=$4 +# User name which exists under hosts user id +EXISTING_CONTAINER_USER_NAME=$(getent passwd "${HOST_USER_ID}" | cut -d: -f1) +EXISTING_CONTAINER_NEW_USER_ID="" +# Group name which exists under hosts group id +EXISTING_CONTAINER_GROUP_NAME=$(getent group "${HOST_GROUP_ID}" | cut -d: -f1) +# User ID of the container user +CONTAINER_USER_ID=$(id -u "${CONTAINER_USER_NAME}") +# Group ID and group name of the container user +CONTAINER_GROUP_ID=$(id -g "${CONTAINER_USER_NAME}") +CONTAINER_GROUP_NAME=$(getent group "${CONTAINER_GROUP_ID}" | cut -d: -f1) +FINAL_GROUP_NAME=${EXISTING_CONTAINER_GROUP_NAME} +SYSTEM_FOLDERS=" \ + ${COREPACK_HOME} \ + ${YARN_CACHE_FOLDER} \ + /etc/nginx \ + /home/node \ + /run/nginx \ + /run/secrets \ + /usr/local/lib/node_modules \ + /usr/local/log \ + /var/log/cron \ + /var/log/nginx \ + /var/run/supervisor \ + /var/www/html \ +" + +# Skip this script if the host user is root +if [ "$HOST_USER" == "root" ]; then + exit 0 +fi + +# Final group name to be used will be container group if no other group exists with the host group ID +if [ -z "${FINAL_GROUP_NAME}" ]; then + FINAL_GROUP_NAME=${CONTAINER_GROUP_NAME} +fi + +# Generate new user ID to be used for existing container user +while [ -n "$(getent passwd "${EXISTING_CONTAINER_NEW_USER_ID}")" ]; do + EXISTING_CONTAINER_NEW_USER_ID=$(shuf -i 1100-2000 -n 1) +done + +# Change user ID for container user if needed +if [ -z "${EXISTING_CONTAINER_USER_NAME}" ] && [ ! "${CONTAINER_USER_ID}" == "${HOST_USER_ID}" ]; then + echo "Changing '${CONTAINER_USER_NAME}' user ID ${CONTAINER_USER_ID} to ${HOST_USER_ID}" + usermod -u "${HOST_USER_ID}" "${CONTAINER_USER_NAME}" +fi + +# Change user ID for existing container user and container user if needed +if [ -n "${EXISTING_CONTAINER_USER_NAME}" ] && [ ! "${EXISTING_CONTAINER_USER_NAME}" == "${CONTAINER_USER_NAME}" ]; then + echo "Changing '${EXISTING_CONTAINER_USER_NAME}' user ID ${HOST_USER_ID} to ${EXISTING_CONTAINER_NEW_USER_ID}" + usermod -u "${EXISTING_CONTAINER_NEW_USER_ID}" "${EXISTING_CONTAINER_USER_NAME}" 2>&1 + echo "Changing '${CONTAINER_USER_NAME}' user ID ${CONTAINER_USER_ID} to ${HOST_USER_ID}" + usermod -u "${HOST_USER_ID}" "${CONTAINER_USER_NAME}" +fi + +# Change group ID for container group name if needed +if [ "${FINAL_GROUP_NAME}" == "${CONTAINER_GROUP_NAME}" ] && [ ! "${CONTAINER_GROUP_ID}" == "${HOST_GROUP_ID}" ]; then + echo "Changing '${FINAL_GROUP_NAME}' group ID ${CONTAINER_GROUP_ID} to ${HOST_GROUP_ID}" + groupmod -g "${HOST_GROUP_ID}" "${FINAL_GROUP_NAME}" + find / -group "${CONTAINER_GROUP_ID}" -exec chgrp -h "${HOST_GROUP_ID}" {} \; +fi + +# Assign correct group for existing container user name if needed +if [ ! "${FINAL_GROUP_NAME}" == "${CONTAINER_GROUP_NAME}" ]; then + echo "Adding user with UID ${HOST_USER_ID} (${CONTAINER_USER_NAME}) to group with GID ${HOST_GROUP_ID} (${FINAL_GROUP_NAME})" + usermod -a -G "${FINAL_GROUP_NAME}" "${CONTAINER_USER_NAME}" 2>&1 +fi + +echo "Setting up system user permissions (${CONTAINER_USER_NAME}:${FINAL_GROUP_NAME})" +for folder in ${SYSTEM_FOLDERS}; do + if [ -d "$folder" ]; then + chown "${CONTAINER_USER_NAME}":"${FINAL_GROUP_NAME}" -R "$folder" + fi +done diff --git a/build/node20/nginx-browsers/Dockerfile b/build/node20/nginx-browsers/Dockerfile new file mode 100644 index 0000000..c3ee41b --- /dev/null +++ b/build/node20/nginx-browsers/Dockerfile @@ -0,0 +1,227 @@ +# +# NOTE: +# THIS DOCKERFILE IS GENERATED VIA "update.sh". +# PLEASE DO NOT EDIT IT DIRECTLY! +# CHECK README FOR MORE INFO. +# +FROM node:20.12.2 + +LABEL maintainer="Lubomir Stanko " + +# ---------------------------------------------------------------------------------------------------------------------- +# ENVIRONMENT VARIABLES +# ---------------------------------------------------------------------------------------------------------------------- +# Common environment variables +ENV CONFIG_OWNER_NAME=node \ + CONFIG_GROUP_NAME=node \ + CONTAINER_STOP_LOG_FILE="/var/www/html/var/log/container_stop.log" \ + COREPACK_HOME="/usr/lib/node/corepack" \ + MAIN_TERMINATED_FILE="/var/www/html/var/log/main-terminated" \ + NPM_CONFIG_LOGLEVEL=notice \ + YARN_CACHE_FOLDER="/var/cache/yarn" \ + YARN_ENABLE_TELEMETRY=0 \ + # Unset yarn version - it could break CI and we don't need it + YARN_VERSION= +# Packages +ENV RUN_DEPS="ca-certificates \ + curl \ + g++ \ + gcc \ + gettext-base \ + git \ + gnupg \ + less \ + logrotate \ + lsb-release \ + make \ + openssh-client \ + procps \ + vim \ + wget" + +# ---------------------------------------------------------------------------------------------------------------------- +# PACKAGES +# ---------------------------------------------------------------------------------------------------------------------- +RUN apt-get update && \ + apt-get install -y \ + ${RUN_DEPS} \ + supervisor=4.2.5-1 && \ +# Cleanup + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# ---------------------------------------------------------------------------------------------------------------------- +# NPM +# Install static npm version +# ---------------------------------------------------------------------------------------------------------------------- +RUN npm install --location=global npm@10.5.2 && \ + npm install --location=global auditjs@4.0.45 && \ + mkdir -p ${COREPACK_HOME} && \ + corepack prepare yarn@4.1.1 --activate && \ + corepack enable && \ +# Node cache cleanup + npm cache clean --force && \ + yarn cache clean --all +# Versions of local tools +RUN echo "node version: $(node -v) \n" \ + "npm version: $(npm -v) \n" \ + "yarn version: $(yarn -v)" + +# ---------------------------------------------------------------------------------------------------------------------- +# USER SETUP +# ---------------------------------------------------------------------------------------------------------------------- +RUN sed -i 's/^#alias l/alias l/g' /home/node/.bashrc && \ + echo "update-notifier=false" > /home/node/.npmrc && \ + mkdir -p \ + ${YARN_CACHE_FOLDER} \ + /usr/local/lib/node_modules \ + /var/run/supervisor \ + /var/www/html/var && \ + chown node:node -R \ + ${COREPACK_HOME} \ + ${YARN_CACHE_FOLDER} \ + /home/node/.npmrc \ + /usr/local/bin \ + /usr/local/lib/node_modules \ + /var/run/supervisor \ + /var/www/html + +#### +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# NGINX SETUP START +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ---------------------------------------------------------------------------------------------------------------------- +# NGINX ENVIRONMENT VARIABLES +ENV NGINX_ACCESS_LOG="/var/log/nginx/access.log main" \ + NGINX_ACCESS_LOG_HEALTHCHECK_ENABLED="0" \ + NGINX_API_LOCATION=/api \ + NGINX_API_V1_LOCATION=/api/v1 \ + NGINX_API_X_ROBOTS_TAG="noindex, nofollow, noarchive, nosnippet" \ + NGINX_CLIENT_MAX_BODY_SIZE=1m \ + NGINX_DOCUMENTS_X_ROBOTS_TAG="noindex, nofollow, noarchive, nosnippet" \ + NGINX_ERROR_LOG="/var/log/nginx/error.log warn" \ + NGINX_HTML_X_ROBOTS_TAG="noindex, nofollow, noarchive, nosnippet" \ + NGINX_KEEPALIVE_REQUESTS=10000 \ + NGINX_KEEPALIVE_TIMEOUT=650 \ + NGINX_LARGE_CLIENT_HEADER_BUFFERS_SIZE=16k \ + NGINX_PORT=8080 \ + NGINX_PROXYPASS_CONFIG=false \ + NGINX_ROOT=/var/www/html/public \ + NGINX_SERVER_TOKENS="off" \ + NGINX_STATIC_CACHE_CONTROL_HEADER="public, max-age=31557600, s-maxage=31557600" \ + NGINX_STATIC_X_CACHE_CONTROL_TTL_HEADER="604800" \ + NGINX_STATIC_X_ROBOTS_TAG="noindex, nofollow, noarchive, nosnippet" \ + NGINX_UPSTREAM_API_PORT=8085 \ + NGINX_UPSTREAM_API_V1_PORT=8285 \ + NGINX_UPSTREAM_WEBSOCKET_PORT=3005 \ + NGINX_WEBSOCKET_LOCATION=/ws \ + NGINX_WEBSOCKET_X_ROBOTS_TAG="noindex, nofollow, noarchive, nosnippet" \ + NGINX_WORKER_CONNECTIONS=1024 \ + NGINX_WORKER_PROCESSES=1 \ + NGINX_WORKER_RLIMIT_NOFILE=65535 \ + NGINX_X_CONTENT_TYPE_OPTIONS="nosniff" \ + NGINX_X_XSS_PROTECTION="1; mode=block" +# ---------------------------------------------------------------------------------------------------------------------- +# NGINX +RUN DEBIAN_FRONTEND=noninteractive && \ + NGINX_KEYRING=/usr/share/keyrings/nginx-archive-keyring.gpg && \ + NGINX_REPO="$(lsb_release -c -s) nginx" && \ + curl -fsSL https://nginx.org/keys/nginx_signing.key | gpg --dearmor -o ${NGINX_KEYRING} && \ + echo "deb [signed-by=${NGINX_KEYRING}] http://nginx.org/packages/debian ${NGINX_REPO}" > /etc/apt/sources.list.d/nginx.list && \ + apt-get update && \ + apt-get install --no-install-recommends --no-install-suggests -y \ + nginx=1.24.0-1~bookworm \ + nginx-module-xslt=1.24.0-1~bookworm \ + nginx-module-geoip=1.24.0-1~bookworm \ + nginx-module-image-filter=1.24.0-1~bookworm \ + nginx-module-njs=1.24.0+0.8.3-1~bookworm && \ +# Cleanup + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# ---------------------------------------------------------------------------------------------------------------------- +# NGINX LOGGING AND USER SETUP +# Create PID folders and forward nginx logs to docker log collector +RUN ln -sf /dev/stdout /var/log/nginx/access.log && \ + ln -sf /dev/stderr /var/log/nginx/error.log && \ + mkdir -p \ + /run/nginx && \ + chown node:node -R \ + /etc/nginx \ + /run/nginx \ + /var/log/nginx +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# NGINX SETUP END +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# BROWSERS SETUP START +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ---------------------------------------------------------------------------------------------------------------------- +ENV DBUS_SESSION_BUS_ADDRESS=/dev/null +# Install Needed packages +RUN apt-get update && \ + apt-get install --no-install-recommends -y \ + bzip2 \ + dbus-x11 \ + fonts-liberation \ + libasound2 \ + libgbm-dev \ + libgbm1 \ + libgconf-2-4 \ + libgtk-3-0 \ + libgtk2.0-0 \ + libnotify-dev \ + libnss3 \ + libu2f-udev \ + libxkbcommon0 \ + libxss1 \ + libxtst6 \ + xauth \ + xdg-utils \ + xvfb && \ +# Cleanup + apt-get clean && \ + rm -r /var/lib/apt/lists/* +# Install Google Chrome +RUN wget -q -O /usr/src/google-chrome-stable_current_amd64.deb "https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_124.0.6367.60-1_amd64.deb" && \ + apt-get update && \ + dpkg -i /usr/src/google-chrome-stable_current_amd64.deb ; \ + apt-get install -f -y && \ + rm -f /usr/src/google-chrome-stable_current_amd64.deb && \ +# Cleanup + apt-get clean && \ + rm -r /var/lib/apt/lists/* +# Install Firefox +RUN wget -q -O /tmp/firefox.tar.bz2 "https://download-installer.cdn.mozilla.net/pub/firefox/releases/125.0.1/linux-x86_64/en-US/firefox-125.0.1.tar.bz2" && \ + tar -C /opt -xjf /tmp/firefox.tar.bz2 && \ + rm -f /tmp/firefox.tar.bz2 && \ + ln -fs /opt/firefox/firefox /usr/bin/firefox +# Versions of local tools +RUN echo "node version: $(node -v) \n" \ + "npm version: $(npm -v) \n" \ + "yarn version: $(yarn -v) \n" \ + "debian version: $(cat /etc/debian_version) \n" \ + "Chrome version: $(google-chrome --version) \n" \ + "Firefox version: $(firefox --version) \n" \ + "git version: $(git --version) \n" \ + "whoami: $(whoami) \n" +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# BROWSERS SETUP END +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +#### + +# ---------------------------------------------------------------------------------------------------------------------- +# RUN CONFIGURATION +# ---------------------------------------------------------------------------------------------------------------------- +COPY --chown=${CONFIG_OWNER_NAME}:${CONFIG_GROUP_NAME} ./etc /etc +COPY --chown=${CONFIG_OWNER_NAME}:${CONFIG_GROUP_NAME} ./usr /usr + +# ---------------------------------------------------------------------------------------------------------------------- +# RUN +# Run setup and entrypoint start +# ---------------------------------------------------------------------------------------------------------------------- +WORKDIR /var/www/html + +USER node + +ENTRYPOINT ["docker-custom-entrypoint"] diff --git a/build/node20/nginx-browsers/etc/fonts/local.conf b/build/node20/nginx-browsers/etc/fonts/local.conf new file mode 100644 index 0000000..07f242c --- /dev/null +++ b/build/node20/nginx-browsers/etc/fonts/local.conf @@ -0,0 +1,34 @@ + + + + + +rgb + + + + +true + + + + +hintslight + + + + +true + + + + +lcddefault + + + + +false + + + \ No newline at end of file diff --git a/build/node20/nginx-browsers/etc/nginx/conf.d/default.conf.template b/build/node20/nginx-browsers/etc/nginx/conf.d/default.conf.template new file mode 100644 index 0000000..6dbca91 --- /dev/null +++ b/build/node20/nginx-browsers/etc/nginx/conf.d/default.conf.template @@ -0,0 +1,58 @@ +map $status $static_cache_control_header { + 404 "no-cache, no-store, must-revalidate"; + default "${NGINX_STATIC_CACHE_CONTROL_HEADER}"; +} + +server { + listen ${NGINX_PORT}; + + root ${NGINX_ROOT}; + index index.html; + client_max_body_size ${NGINX_CLIENT_MAX_BODY_SIZE}; + large_client_header_buffers 4 ${NGINX_LARGE_CLIENT_HEADER_BUFFERS_SIZE}; + + #App paths + location / { + try_files $uri $uri/ @rewrites; + } + + location @rewrites { + rewrite ^(.+)$ /index.html last; + } + + location ~* \.html?$ { + expires -1; + add_header Pragma "no-cache"; + add_header Cache-Control "no-store, must-revalidate"; + add_header Host $host; + + # Custom nginx response headers + add_header 'X-Robots-Tag' '${NGINX_HTML_X_ROBOTS_TAG}' always; + add_header 'X-XSS-Protection' '${NGINX_X_XSS_PROTECTION}' always; + add_header 'X-Content-Type-Options' '${NGINX_X_CONTENT_TYPE_OPTIONS}' always; + } + + location ~* ^[^\?\&]+\.(json|zip|tgz|gz|rar|bz2|doc|xls|pdf|ppt|txt|tar|rtf|mid|midi|wav)$ { + add_header Host $host; + + # Custom nginx response headers + add_header 'X-Robots-Tag' '${NGINX_DOCUMENTS_X_ROBOTS_TAG}' always; + add_header 'X-XSS-Protection' '${NGINX_X_XSS_PROTECTION}' always; + add_header 'X-Content-Type-Options' '${NGINX_X_CONTENT_TYPE_OPTIONS}' always; + } + + location ~* ^[^\?\&]+\.(jpg|jpeg|gif|png|ico|css|js|svg|bmp|eot|woff|woff2|ttf)$ { + add_header Host $host; + add_header Cache-Control $static_cache_control_header always; + add_header X-Cache-Control-TTL ${NGINX_STATIC_X_CACHE_CONTROL_TTL_HEADER}; + + # Custom nginx response headers + add_header 'X-Robots-Tag' '${NGINX_STATIC_X_ROBOTS_TAG}' always; + add_header 'X-XSS-Protection' '${NGINX_X_XSS_PROTECTION}' always; + add_header 'X-Content-Type-Options' '${NGINX_X_CONTENT_TYPE_OPTIONS}' always; + } + + location ~ /\.ht { + deny all; + } +} diff --git a/build/node20/nginx-browsers/etc/nginx/conf.d/proxy.conf.template b/build/node20/nginx-browsers/etc/nginx/conf.d/proxy.conf.template new file mode 100644 index 0000000..4f5b34d --- /dev/null +++ b/build/node20/nginx-browsers/etc/nginx/conf.d/proxy.conf.template @@ -0,0 +1,65 @@ +map $http_upgrade $connection_upgrade { + '' close; + default upgrade; +} + +upstream websocket { + server 127.0.0.1:${NGINX_UPSTREAM_WEBSOCKET_PORT}; +} + +upstream api { + server 127.0.0.1:${NGINX_UPSTREAM_API_PORT}; +} + +upstream apiv1 { + server 127.0.0.1:${NGINX_UPSTREAM_API_V1_PORT}; +} + +server { + listen ${NGINX_PORT}; + + sendfile off; + client_max_body_size ${NGINX_CLIENT_MAX_BODY_SIZE}; + large_client_header_buffers 4 ${NGINX_LARGE_CLIENT_HEADER_BUFFERS_SIZE}; + + #App paths + location ${NGINX_WEBSOCKET_LOCATION} { + proxy_pass http://websocket; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $host; + + # Custom nginx response headers + proxy_hide_header X-Robots-Tag; + add_header 'X-Robots-Tag' '${NGINX_WEBSOCKET_X_ROBOTS_TAG}' always; + add_header 'X-XSS-Protection' '${NGINX_X_XSS_PROTECTION}' always; + add_header 'X-Content-Type-Options' '${NGINX_X_CONTENT_TYPE_OPTIONS}' always; + } + + location ${NGINX_API_LOCATION} { + proxy_pass http://api; + proxy_set_header Host $host; + + # Custom nginx response headers + proxy_hide_header X-Robots-Tag; + add_header 'X-Robots-Tag' '${NGINX_API_X_ROBOTS_TAG}' always; + add_header 'X-XSS-Protection' '${NGINX_X_XSS_PROTECTION}' always; + add_header 'X-Content-Type-Options' '${NGINX_X_CONTENT_TYPE_OPTIONS}' always; + } + + location ${NGINX_API_V1_LOCATION} { + proxy_pass http://apiv1; + proxy_set_header Host $host; + + # Custom nginx response headers + proxy_hide_header X-Robots-Tag; + add_header 'X-Robots-Tag' '${NGINX_API_X_ROBOTS_TAG}' always; + add_header 'X-XSS-Protection' '${NGINX_X_XSS_PROTECTION}' always; + add_header 'X-Content-Type-Options' '${NGINX_X_CONTENT_TYPE_OPTIONS}' always; + } + + location ~ /\.ht { + deny all; + } +} diff --git a/build/node20/nginx-browsers/etc/nginx/nginx.conf.template b/build/node20/nginx-browsers/etc/nginx/nginx.conf.template new file mode 100644 index 0000000..ae73604 --- /dev/null +++ b/build/node20/nginx-browsers/etc/nginx/nginx.conf.template @@ -0,0 +1,48 @@ +worker_processes ${NGINX_WORKER_PROCESSES}; +worker_rlimit_nofile ${NGINX_WORKER_RLIMIT_NOFILE}; + +error_log ${NGINX_ERROR_LOG}; +pid /run/nginx/nginx.pid; + +events { + worker_connections ${NGINX_WORKER_CONNECTIONS}; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + map $http_x_forwarded_proto $real_scheme { + default $http_x_forwarded_proto; + '' $scheme; + } + log_format main '$remote_addr - $remote_user [$time_local] "$request_method $real_scheme://$host$request_uri $server_protocol" $status ' + '$body_bytes_sent' + 'B ' + '$request_time' + 's ' + '"$http_referer" "$http_user_agent" "$http_x_forwarded_for" "$http_x_forwarded_proto"'; + client_body_temp_path /tmp/client_temp; + proxy_temp_path /tmp/proxy_temp_path; + fastcgi_temp_path /tmp/fastcgi_temp; + uwsgi_temp_path /tmp/uwsgi_temp; + scgi_temp_path /tmp/scgi_temp; + + map $status:$http_user_agent $loggable { + ~200:kube-probe ${NGINX_ACCESS_LOG_HEALTHCHECK_ENABLED}; + ~200:GoogleHC ${NGINX_ACCESS_LOG_HEALTHCHECK_ENABLED}; + default 1; + } + access_log ${NGINX_ACCESS_LOG} if=$loggable; + + server_tokens ${NGINX_SERVER_TOKENS}; + keepalive_timeout ${NGINX_KEEPALIVE_TIMEOUT}; + keepalive_requests ${NGINX_KEEPALIVE_REQUESTS}; + sendfile on; + + gzip on; + gzip_proxied any; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + + include /etc/nginx/conf.d/*.conf; +} diff --git a/build/node20/nginx-browsers/etc/supervisor/conf.d/nginx.conf b/build/node20/nginx-browsers/etc/supervisor/conf.d/nginx.conf new file mode 100644 index 0000000..5fe4411 --- /dev/null +++ b/build/node20/nginx-browsers/etc/supervisor/conf.d/nginx.conf @@ -0,0 +1,13 @@ +[program:nginx] +command=/usr/sbin/nginx -g "daemon off;" +autostart=true +autorestart=unexpected +# Expect 0 exit code returned when stopping the container +exitcodes=0 +priority=10 +stdout_events_enabled=true +stderr_events_enabled=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 diff --git a/build/node20/nginx-browsers/etc/supervisor/supervisord.conf b/build/node20/nginx-browsers/etc/supervisor/supervisord.conf new file mode 100644 index 0000000..dae2dc2 --- /dev/null +++ b/build/node20/nginx-browsers/etc/supervisor/supervisord.conf @@ -0,0 +1,23 @@ +[unix_http_server] +file=/var/run/supervisor/supervisor.sock +chmod=0700 +username=docker +password=docker + +[supervisorctl] +serverurl=unix:///var/run/supervisor/supervisor.sock +username=docker +password=docker + +[supervisord] +nodaemon=true +logfile=/dev/stdout +logfile_maxbytes=0 +pidfile=/var/run/supervisor/supervisord.pid +loglevel=info + +[rpcinterface:supervisor] +supervisor.rpcinterface_factory=supervisor.rpcinterface:make_main_rpcinterface + +[include] +files = /etc/supervisor/conf.d/*.conf diff --git a/build/node20/nginx-browsers/usr/local/bin/docker-custom-entrypoint b/build/node20/nginx-browsers/usr/local/bin/docker-custom-entrypoint new file mode 100755 index 0000000..12f7cdb --- /dev/null +++ b/build/node20/nginx-browsers/usr/local/bin/docker-custom-entrypoint @@ -0,0 +1,56 @@ +#!/bin/bash + +NGINX_CONF_TEMPLATE="/etc/nginx/nginx.conf.template" +NGINX_CONF="/etc/nginx/nginx.conf" +NGINX_DEFAULT_CONF_TEMPLATE="/etc/nginx/conf.d/default.conf.template" +NGINX_PROXY_CONF_TEMPLATE="/etc/nginx/conf.d/proxy.conf.template" +NGINX_DEFAULT_CONF="/etc/nginx/conf.d/default.conf" + +if [ -f ${NGINX_CONF_TEMPLATE} ]; then + echo "[INFO] Setup nginx \"${NGINX_CONF}\" config file" + envsubst "\ + \${NGINX_ACCESS_LOG_HEALTHCHECK_ENABLED} \ + \${NGINX_ACCESS_LOG} \ + \${NGINX_ERROR_LOG} \ + \${NGINX_KEEPALIVE_REQUESTS} \ + \${NGINX_KEEPALIVE_TIMEOUT} \ + \${NGINX_SERVER_TOKENS} \ + \${NGINX_WORKER_CONNECTIONS} \ + \${NGINX_WORKER_PROCESSES} \ + \${NGINX_WORKER_RLIMIT_NOFILE} \ + " <${NGINX_CONF_TEMPLATE} >${NGINX_CONF} +fi +if ${NGINX_PROXYPASS_CONFIG} && [ -f "${NGINX_PROXY_CONF_TEMPLATE}" ]; then + echo "[INFO] Setup nginx proxy \"${NGINX_DEFAULT_CONF}\" config file" + envsubst "\ + \${NGINX_API_LOCATION} \ + \${NGINX_API_V1_LOCATION} \ + \${NGINX_API_X_ROBOTS_TAG} \ + \${NGINX_CLIENT_MAX_BODY_SIZE} \ + \${NGINX_LARGE_CLIENT_HEADER_BUFFERS_SIZE} \ + \${NGINX_PORT} \ + \${NGINX_UPSTREAM_API_PORT} \ + \${NGINX_UPSTREAM_API_V1_PORT} \ + \${NGINX_UPSTREAM_WEBSOCKET_PORT} \ + \${NGINX_WEBSOCKET_LOCATION} \ + \${NGINX_WEBSOCKET_X_ROBOTS_TAG} \ + \${NGINX_X_CONTENT_TYPE_OPTIONS} \ + \${NGINX_X_XSS_PROTECTION} \ + " <${NGINX_PROXY_CONF_TEMPLATE} >${NGINX_DEFAULT_CONF} +elif [ -f "${NGINX_DEFAULT_CONF_TEMPLATE}" ]; then + echo "[INFO] Setup nginx default \"${NGINX_DEFAULT_CONF}\" config file" + envsubst "\ + \${NGINX_CLIENT_MAX_BODY_SIZE} \ + \${NGINX_DOCUMENTS_X_ROBOTS_TAG} \ + \${NGINX_HTML_X_ROBOTS_TAG} \ + \${NGINX_LARGE_CLIENT_HEADER_BUFFERS_SIZE} \ + \${NGINX_PORT} \ + \${NGINX_ROOT} \ + \${NGINX_STATIC_CACHE_CONTROL_HEADER} \ + \${NGINX_STATIC_X_CACHE_CONTROL_TTL_HEADER} \ + \${NGINX_STATIC_X_ROBOTS_TAG} \ + \${NGINX_X_CONTENT_TYPE_OPTIONS} \ + \${NGINX_X_XSS_PROTECTION} \ + " <${NGINX_DEFAULT_CONF_TEMPLATE} >${NGINX_DEFAULT_CONF} +fi +exec "$@" diff --git a/build/node20/nginx-browsers/usr/local/bin/env-config b/build/node20/nginx-browsers/usr/local/bin/env-config new file mode 100755 index 0000000..36ce7d3 --- /dev/null +++ b/build/node20/nginx-browsers/usr/local/bin/env-config @@ -0,0 +1,178 @@ +#!/bin/bash +# Script used to run the setup of config.json file using .env files and exported variables: +# - if the variable is exported in system, then it is left untouched and not exported from any env file +# - all missing variables are exported from main env file if exists +# - all missing variables (the same as from main env file) are exported from local env file if exists +set -euo pipefail + +CONFIG_DIST_PATH="public/config.json.dist" +CONFIG_PATH="public/config.json" +DEBUG=false +END_FLOW=false +ENV_APPLICATION_FILE_DIST=".env" +ENV_APPLICATION_FILE=".env.local" +PROJECT_ROOT="/var/www/html" + +function echo_debug() { + if ${DEBUG}; then + echo -e "[DEBUG] $*" + fi +} + +function help() { + echo -en "$(printf %q "${BASH_SOURCE[0]}") [OPERATION] [OPTIONS]... + \nBash script to run env config.json setup inside the container. + + Options: + --config-dist-path (Optional) Path to existing config.json.dist file + Default: public/config.json.dist + --config-path (Optional) Path to config.json file to be created + Default: public/config.json + --debug (Optional) Whether to enable debug output + --env-dist-file (Optional) Path to existing .env file + Default: .env + --env-file (Optional) Path to .env.local file to be created + Default: .env.local + --project-root (Optional) Path project home where to find config and env files + Default: /var/www/html + + Examples: + $(printf %q "${BASH_SOURCE[0]}") --config-dist-path dist/config.json.dist --config-path dist/config.json + \n" +} + +function set_vars_from_file() { + if [ -z "${MISSING_VARIABLE_NAMES}" ]; then + echo_debug "All variables are exported, there is nothing needed to export from $1 file" + return + fi + while IFS= read -r variable; do + variable_name=$(echo "${variable?}" | cut -d "=" -f 1) + # Export only missing variables + if echo "${MISSING_VARIABLE_NAMES}" | grep -q -w "$variable_name"; then + ENV_VARS+=("${variable?}") + echo_debug "Preparing variable for export: ${variable?}" + fi + done < <(grep -v "^#" "$1" | sed '/^$/d') + echo_debug "Prepared variables for export:" "${ENV_VARS[@]}" + for var in "${ENV_VARS[@]}"; do export "${var?}"; done +} + +while [ "$#" -gt 0 ]; do + case $1 in + --config-path) + CONFIG_PATH=$2 + shift 2 + continue + ;; + --config-path=*) + CONFIG_PATH=${1#*=} + shift 1 + continue + ;; + --config-dist-path) + CONFIG_DIST_PATH=$2 + shift 2 + continue + ;; + --config-dist-path=*) + CONFIG_DIST_PATH=${1#*=} + shift 1 + continue + ;; + --debug) + DEBUG=true + shift 1 + continue + ;; + --env-file) + ENV_APPLICATION_FILE=$2 + shift 2 + continue + ;; + --env-file=*) + ENV_APPLICATION_FILE=${1#*=} + shift 1 + continue + ;; + --env-dist-file) + ENV_APPLICATION_FILE_DIST=$2 + shift 2 + continue + ;; + --env-dist-file=*) + ENV_APPLICATION_FILE_DIST=${1#*=} + shift 1 + continue + ;; + --project-root) + PROJECT_ROOT=$2 + shift 2 + continue + ;; + --project-root=*) + PROJECT_ROOT=${1#*=} + shift 1 + continue + ;; + -h | --help) + help + exit + ;; + *) + echo "[WARN] Unknown command line switch: $1" >&2 + help + exit + ;; + esac +done + +if [ ! -f "${CONFIG_DIST_PATH}" ]; then + echo "[INFO] Project root: ${PROJECT_ROOT}" + echo "[ERROR] File ${CONFIG_DIST_PATH} does not exist, provide correct path using --config-dist-path option" + END_FLOW=true +fi + +if [ ! -f "${ENV_APPLICATION_FILE_DIST}" ]; then + echo "[INFO] Project root: ${PROJECT_ROOT}" + echo "[ERROR] File ${ENV_APPLICATION_FILE_DIST} does not exist, provide correct path using --env-dist-path option" + END_FLOW=true +fi + +if ${END_FLOW}; then + echo -e "\n" + help + exit 1 +fi + +cd "${PROJECT_ROOT}" || exit 1 + +# Get all variables from main env file +ALL_VARIABLE_NAMES=$(grep -v "^#" "${ENV_APPLICATION_FILE_DIST}" | sed -e '/^$/d' | cut -d "=" -f 1) +# Set missing variables to all variables, we will filter them later in the script +MISSING_VARIABLE_NAMES=${ALL_VARIABLE_NAMES} + +# Remove already exported (existing) variables from missing variables to make the list clear +for variable_name in ${ALL_VARIABLE_NAMES}; do + if [ -n "${!variable_name+x}" ]; then + MISSING_VARIABLE_NAMES=${MISSING_VARIABLE_NAMES//$variable_name/} + echo_debug "Variable already exported: ${variable_name?}" + fi +done +MISSING_VARIABLE_NAMES="$(echo "${MISSING_VARIABLE_NAMES}" | sed '/^$/d' | tr '\n' ' ')" +echo_debug "Variables to read from env files: ${MISSING_VARIABLE_NAMES}" + +# Export missing variables from main env file if exists +if [ -f "${ENV_APPLICATION_FILE_DIST}" ]; then + echo_debug "Reading variables from ${ENV_APPLICATION_FILE_DIST} file" + set_vars_from_file "${ENV_APPLICATION_FILE_DIST}" +fi + +# Export missing variables from local env file if exists +if [ -f "${ENV_APPLICATION_FILE}" ]; then + echo_debug "Reading variables from ${ENV_APPLICATION_FILE} file" + set_vars_from_file "${ENV_APPLICATION_FILE}" +fi + +mkdir -p "$(dirname "${CONFIG_PATH}")" +envsubst <"${CONFIG_DIST_PATH}" >"${CONFIG_PATH}" diff --git a/build/node20/nginx-browsers/usr/local/bin/fix-user b/build/node20/nginx-browsers/usr/local/bin/fix-user new file mode 100755 index 0000000..fafd384 --- /dev/null +++ b/build/node20/nginx-browsers/usr/local/bin/fix-user @@ -0,0 +1,97 @@ +#!/bin/bash +# +# Corrects UID and GID for container user according to host UID and GID if needed +# +# #1 Example: UID 1000 and GID 1000: +# ./fix-user test user 1000 1000 +# - 'user' has UID 1000 and GID 1000 by default - nothing will happen +# +# #2 Example: UID 501 (not exists in docker image) and GID 501 (not exists in docker image): +# ./fix-user test user 501 501 +# - 'user' UID will change to 501 +# - 'user' GID will change to 501 +# +# #3 Example: UID 35 (exists in docker image as 'games') and GID 100 (exists in docker image as 'users'): +# ./fix-user test user 35 100 +# - 'games' UID will change to random free UID (1100-2000) to release UID 35 +# - 'user' UID will change to 35 +# - adds user 'user' to group 'users' + +HOST_USER=$1 +CONTAINER_USER_NAME=$2 +HOST_USER_ID=$3 +HOST_GROUP_ID=$4 +# User name which exists under hosts user id +EXISTING_CONTAINER_USER_NAME=$(getent passwd "${HOST_USER_ID}" | cut -d: -f1) +EXISTING_CONTAINER_NEW_USER_ID="" +# Group name which exists under hosts group id +EXISTING_CONTAINER_GROUP_NAME=$(getent group "${HOST_GROUP_ID}" | cut -d: -f1) +# User ID of the container user +CONTAINER_USER_ID=$(id -u "${CONTAINER_USER_NAME}") +# Group ID and group name of the container user +CONTAINER_GROUP_ID=$(id -g "${CONTAINER_USER_NAME}") +CONTAINER_GROUP_NAME=$(getent group "${CONTAINER_GROUP_ID}" | cut -d: -f1) +FINAL_GROUP_NAME=${EXISTING_CONTAINER_GROUP_NAME} +SYSTEM_FOLDERS=" \ + ${COREPACK_HOME} \ + ${YARN_CACHE_FOLDER} \ + /etc/nginx \ + /home/node \ + /run/nginx \ + /run/secrets \ + /usr/local/lib/node_modules \ + /usr/local/log \ + /var/log/cron \ + /var/log/nginx \ + /var/run/supervisor \ + /var/www/html \ +" + +# Skip this script if the host user is root +if [ "$HOST_USER" == "root" ]; then + exit 0 +fi + +# Final group name to be used will be container group if no other group exists with the host group ID +if [ -z "${FINAL_GROUP_NAME}" ]; then + FINAL_GROUP_NAME=${CONTAINER_GROUP_NAME} +fi + +# Generate new user ID to be used for existing container user +while [ -n "$(getent passwd "${EXISTING_CONTAINER_NEW_USER_ID}")" ]; do + EXISTING_CONTAINER_NEW_USER_ID=$(shuf -i 1100-2000 -n 1) +done + +# Change user ID for container user if needed +if [ -z "${EXISTING_CONTAINER_USER_NAME}" ] && [ ! "${CONTAINER_USER_ID}" == "${HOST_USER_ID}" ]; then + echo "Changing '${CONTAINER_USER_NAME}' user ID ${CONTAINER_USER_ID} to ${HOST_USER_ID}" + usermod -u "${HOST_USER_ID}" "${CONTAINER_USER_NAME}" +fi + +# Change user ID for existing container user and container user if needed +if [ -n "${EXISTING_CONTAINER_USER_NAME}" ] && [ ! "${EXISTING_CONTAINER_USER_NAME}" == "${CONTAINER_USER_NAME}" ]; then + echo "Changing '${EXISTING_CONTAINER_USER_NAME}' user ID ${HOST_USER_ID} to ${EXISTING_CONTAINER_NEW_USER_ID}" + usermod -u "${EXISTING_CONTAINER_NEW_USER_ID}" "${EXISTING_CONTAINER_USER_NAME}" 2>&1 + echo "Changing '${CONTAINER_USER_NAME}' user ID ${CONTAINER_USER_ID} to ${HOST_USER_ID}" + usermod -u "${HOST_USER_ID}" "${CONTAINER_USER_NAME}" +fi + +# Change group ID for container group name if needed +if [ "${FINAL_GROUP_NAME}" == "${CONTAINER_GROUP_NAME}" ] && [ ! "${CONTAINER_GROUP_ID}" == "${HOST_GROUP_ID}" ]; then + echo "Changing '${FINAL_GROUP_NAME}' group ID ${CONTAINER_GROUP_ID} to ${HOST_GROUP_ID}" + groupmod -g "${HOST_GROUP_ID}" "${FINAL_GROUP_NAME}" + find / -group "${CONTAINER_GROUP_ID}" -exec chgrp -h "${HOST_GROUP_ID}" {} \; +fi + +# Assign correct group for existing container user name if needed +if [ ! "${FINAL_GROUP_NAME}" == "${CONTAINER_GROUP_NAME}" ]; then + echo "Adding user with UID ${HOST_USER_ID} (${CONTAINER_USER_NAME}) to group with GID ${HOST_GROUP_ID} (${FINAL_GROUP_NAME})" + usermod -a -G "${FINAL_GROUP_NAME}" "${CONTAINER_USER_NAME}" 2>&1 +fi + +echo "Setting up system user permissions (${CONTAINER_USER_NAME}:${FINAL_GROUP_NAME})" +for folder in ${SYSTEM_FOLDERS}; do + if [ -d "$folder" ]; then + chown "${CONTAINER_USER_NAME}":"${FINAL_GROUP_NAME}" -R "$folder" + fi +done diff --git a/build/node20/nginx-browsers/usr/share/dbus-1/system.conf b/build/node20/nginx-browsers/usr/share/dbus-1/system.conf new file mode 100644 index 0000000..614ca75 --- /dev/null +++ b/build/node20/nginx-browsers/usr/share/dbus-1/system.conf @@ -0,0 +1,141 @@ + + + + + + + + + system + + + node + + + + + + + + + /usr/lib/dbus-1.0/dbus-daemon-launch-helper + + + /run/dbus/pid + + + + + + EXTERNAL + + + unix:path=/run/dbus/system_bus_socket + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /etc/dbus-1/system.conf.dpkg-bak + + + + + + + + + + + + + + + + + + + + + system.d + + /etc/dbus-1/system.d + + + /etc/dbus-1/system-local.conf + + contexts/dbus_contexts + + \ No newline at end of file diff --git a/build/node20/nginx/Dockerfile b/build/node20/nginx/Dockerfile new file mode 100644 index 0000000..8e37485 --- /dev/null +++ b/build/node20/nginx/Dockerfile @@ -0,0 +1,172 @@ +# +# NOTE: +# THIS DOCKERFILE IS GENERATED VIA "update.sh". +# PLEASE DO NOT EDIT IT DIRECTLY! +# CHECK README FOR MORE INFO. +# +FROM node:20.12.2 + +LABEL maintainer="Lubomir Stanko " + +# ---------------------------------------------------------------------------------------------------------------------- +# ENVIRONMENT VARIABLES +# ---------------------------------------------------------------------------------------------------------------------- +# Common environment variables +ENV CONFIG_OWNER_NAME=node \ + CONFIG_GROUP_NAME=node \ + CONTAINER_STOP_LOG_FILE="/var/www/html/var/log/container_stop.log" \ + COREPACK_HOME="/usr/lib/node/corepack" \ + MAIN_TERMINATED_FILE="/var/www/html/var/log/main-terminated" \ + NPM_CONFIG_LOGLEVEL=notice \ + YARN_CACHE_FOLDER="/var/cache/yarn" \ + YARN_ENABLE_TELEMETRY=0 \ + # Unset yarn version - it could break CI and we don't need it + YARN_VERSION= +# Packages +ENV RUN_DEPS="ca-certificates \ + curl \ + g++ \ + gcc \ + gettext-base \ + git \ + gnupg \ + less \ + logrotate \ + lsb-release \ + make \ + openssh-client \ + procps \ + vim \ + wget" + +# ---------------------------------------------------------------------------------------------------------------------- +# PACKAGES +# ---------------------------------------------------------------------------------------------------------------------- +RUN apt-get update && \ + apt-get install -y \ + ${RUN_DEPS} \ + supervisor=4.2.5-1 && \ +# Cleanup + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# ---------------------------------------------------------------------------------------------------------------------- +# NPM +# Install static npm version +# ---------------------------------------------------------------------------------------------------------------------- +RUN npm install --location=global npm@10.5.2 && \ + npm install --location=global auditjs@4.0.45 && \ + mkdir -p ${COREPACK_HOME} && \ + corepack prepare yarn@4.1.1 --activate && \ + corepack enable && \ +# Node cache cleanup + npm cache clean --force && \ + yarn cache clean --all +# Versions of local tools +RUN echo "node version: $(node -v) \n" \ + "npm version: $(npm -v) \n" \ + "yarn version: $(yarn -v)" + +# ---------------------------------------------------------------------------------------------------------------------- +# USER SETUP +# ---------------------------------------------------------------------------------------------------------------------- +RUN sed -i 's/^#alias l/alias l/g' /home/node/.bashrc && \ + echo "update-notifier=false" > /home/node/.npmrc && \ + mkdir -p \ + ${YARN_CACHE_FOLDER} \ + /usr/local/lib/node_modules \ + /var/run/supervisor \ + /var/www/html/var && \ + chown node:node -R \ + ${COREPACK_HOME} \ + ${YARN_CACHE_FOLDER} \ + /home/node/.npmrc \ + /usr/local/bin \ + /usr/local/lib/node_modules \ + /var/run/supervisor \ + /var/www/html + +#### +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# NGINX SETUP START +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ---------------------------------------------------------------------------------------------------------------------- +# NGINX ENVIRONMENT VARIABLES +ENV NGINX_ACCESS_LOG="/var/log/nginx/access.log main" \ + NGINX_ACCESS_LOG_HEALTHCHECK_ENABLED="0" \ + NGINX_API_LOCATION=/api \ + NGINX_API_V1_LOCATION=/api/v1 \ + NGINX_API_X_ROBOTS_TAG="noindex, nofollow, noarchive, nosnippet" \ + NGINX_CLIENT_MAX_BODY_SIZE=1m \ + NGINX_DOCUMENTS_X_ROBOTS_TAG="noindex, nofollow, noarchive, nosnippet" \ + NGINX_ERROR_LOG="/var/log/nginx/error.log warn" \ + NGINX_HTML_X_ROBOTS_TAG="noindex, nofollow, noarchive, nosnippet" \ + NGINX_KEEPALIVE_REQUESTS=10000 \ + NGINX_KEEPALIVE_TIMEOUT=650 \ + NGINX_LARGE_CLIENT_HEADER_BUFFERS_SIZE=16k \ + NGINX_PORT=8080 \ + NGINX_PROXYPASS_CONFIG=false \ + NGINX_ROOT=/var/www/html/public \ + NGINX_SERVER_TOKENS="off" \ + NGINX_STATIC_CACHE_CONTROL_HEADER="public, max-age=31557600, s-maxage=31557600" \ + NGINX_STATIC_X_CACHE_CONTROL_TTL_HEADER="604800" \ + NGINX_STATIC_X_ROBOTS_TAG="noindex, nofollow, noarchive, nosnippet" \ + NGINX_UPSTREAM_API_PORT=8085 \ + NGINX_UPSTREAM_API_V1_PORT=8285 \ + NGINX_UPSTREAM_WEBSOCKET_PORT=3005 \ + NGINX_WEBSOCKET_LOCATION=/ws \ + NGINX_WEBSOCKET_X_ROBOTS_TAG="noindex, nofollow, noarchive, nosnippet" \ + NGINX_WORKER_CONNECTIONS=1024 \ + NGINX_WORKER_PROCESSES=1 \ + NGINX_WORKER_RLIMIT_NOFILE=65535 \ + NGINX_X_CONTENT_TYPE_OPTIONS="nosniff" \ + NGINX_X_XSS_PROTECTION="1; mode=block" +# ---------------------------------------------------------------------------------------------------------------------- +# NGINX +RUN DEBIAN_FRONTEND=noninteractive && \ + NGINX_KEYRING=/usr/share/keyrings/nginx-archive-keyring.gpg && \ + NGINX_REPO="$(lsb_release -c -s) nginx" && \ + curl -fsSL https://nginx.org/keys/nginx_signing.key | gpg --dearmor -o ${NGINX_KEYRING} && \ + echo "deb [signed-by=${NGINX_KEYRING}] http://nginx.org/packages/debian ${NGINX_REPO}" > /etc/apt/sources.list.d/nginx.list && \ + apt-get update && \ + apt-get install --no-install-recommends --no-install-suggests -y \ + nginx=1.24.0-1~bookworm \ + nginx-module-xslt=1.24.0-1~bookworm \ + nginx-module-geoip=1.24.0-1~bookworm \ + nginx-module-image-filter=1.24.0-1~bookworm \ + nginx-module-njs=1.24.0+0.8.3-1~bookworm && \ +# Cleanup + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# ---------------------------------------------------------------------------------------------------------------------- +# NGINX LOGGING AND USER SETUP +# Create PID folders and forward nginx logs to docker log collector +RUN ln -sf /dev/stdout /var/log/nginx/access.log && \ + ln -sf /dev/stderr /var/log/nginx/error.log && \ + mkdir -p \ + /run/nginx && \ + chown node:node -R \ + /etc/nginx \ + /run/nginx \ + /var/log/nginx +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# NGINX SETUP END +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +#### + +# ---------------------------------------------------------------------------------------------------------------------- +# RUN CONFIGURATION +# ---------------------------------------------------------------------------------------------------------------------- +COPY --chown=${CONFIG_OWNER_NAME}:${CONFIG_GROUP_NAME} ./etc /etc +COPY --chown=${CONFIG_OWNER_NAME}:${CONFIG_GROUP_NAME} ./usr /usr + +# ---------------------------------------------------------------------------------------------------------------------- +# RUN +# Run setup and entrypoint start +# ---------------------------------------------------------------------------------------------------------------------- +WORKDIR /var/www/html + +USER node + +ENTRYPOINT ["docker-custom-entrypoint"] diff --git a/build/node20/nginx/etc/nginx/conf.d/default.conf.template b/build/node20/nginx/etc/nginx/conf.d/default.conf.template new file mode 100644 index 0000000..6dbca91 --- /dev/null +++ b/build/node20/nginx/etc/nginx/conf.d/default.conf.template @@ -0,0 +1,58 @@ +map $status $static_cache_control_header { + 404 "no-cache, no-store, must-revalidate"; + default "${NGINX_STATIC_CACHE_CONTROL_HEADER}"; +} + +server { + listen ${NGINX_PORT}; + + root ${NGINX_ROOT}; + index index.html; + client_max_body_size ${NGINX_CLIENT_MAX_BODY_SIZE}; + large_client_header_buffers 4 ${NGINX_LARGE_CLIENT_HEADER_BUFFERS_SIZE}; + + #App paths + location / { + try_files $uri $uri/ @rewrites; + } + + location @rewrites { + rewrite ^(.+)$ /index.html last; + } + + location ~* \.html?$ { + expires -1; + add_header Pragma "no-cache"; + add_header Cache-Control "no-store, must-revalidate"; + add_header Host $host; + + # Custom nginx response headers + add_header 'X-Robots-Tag' '${NGINX_HTML_X_ROBOTS_TAG}' always; + add_header 'X-XSS-Protection' '${NGINX_X_XSS_PROTECTION}' always; + add_header 'X-Content-Type-Options' '${NGINX_X_CONTENT_TYPE_OPTIONS}' always; + } + + location ~* ^[^\?\&]+\.(json|zip|tgz|gz|rar|bz2|doc|xls|pdf|ppt|txt|tar|rtf|mid|midi|wav)$ { + add_header Host $host; + + # Custom nginx response headers + add_header 'X-Robots-Tag' '${NGINX_DOCUMENTS_X_ROBOTS_TAG}' always; + add_header 'X-XSS-Protection' '${NGINX_X_XSS_PROTECTION}' always; + add_header 'X-Content-Type-Options' '${NGINX_X_CONTENT_TYPE_OPTIONS}' always; + } + + location ~* ^[^\?\&]+\.(jpg|jpeg|gif|png|ico|css|js|svg|bmp|eot|woff|woff2|ttf)$ { + add_header Host $host; + add_header Cache-Control $static_cache_control_header always; + add_header X-Cache-Control-TTL ${NGINX_STATIC_X_CACHE_CONTROL_TTL_HEADER}; + + # Custom nginx response headers + add_header 'X-Robots-Tag' '${NGINX_STATIC_X_ROBOTS_TAG}' always; + add_header 'X-XSS-Protection' '${NGINX_X_XSS_PROTECTION}' always; + add_header 'X-Content-Type-Options' '${NGINX_X_CONTENT_TYPE_OPTIONS}' always; + } + + location ~ /\.ht { + deny all; + } +} diff --git a/build/node20/nginx/etc/nginx/conf.d/proxy.conf.template b/build/node20/nginx/etc/nginx/conf.d/proxy.conf.template new file mode 100644 index 0000000..4f5b34d --- /dev/null +++ b/build/node20/nginx/etc/nginx/conf.d/proxy.conf.template @@ -0,0 +1,65 @@ +map $http_upgrade $connection_upgrade { + '' close; + default upgrade; +} + +upstream websocket { + server 127.0.0.1:${NGINX_UPSTREAM_WEBSOCKET_PORT}; +} + +upstream api { + server 127.0.0.1:${NGINX_UPSTREAM_API_PORT}; +} + +upstream apiv1 { + server 127.0.0.1:${NGINX_UPSTREAM_API_V1_PORT}; +} + +server { + listen ${NGINX_PORT}; + + sendfile off; + client_max_body_size ${NGINX_CLIENT_MAX_BODY_SIZE}; + large_client_header_buffers 4 ${NGINX_LARGE_CLIENT_HEADER_BUFFERS_SIZE}; + + #App paths + location ${NGINX_WEBSOCKET_LOCATION} { + proxy_pass http://websocket; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $host; + + # Custom nginx response headers + proxy_hide_header X-Robots-Tag; + add_header 'X-Robots-Tag' '${NGINX_WEBSOCKET_X_ROBOTS_TAG}' always; + add_header 'X-XSS-Protection' '${NGINX_X_XSS_PROTECTION}' always; + add_header 'X-Content-Type-Options' '${NGINX_X_CONTENT_TYPE_OPTIONS}' always; + } + + location ${NGINX_API_LOCATION} { + proxy_pass http://api; + proxy_set_header Host $host; + + # Custom nginx response headers + proxy_hide_header X-Robots-Tag; + add_header 'X-Robots-Tag' '${NGINX_API_X_ROBOTS_TAG}' always; + add_header 'X-XSS-Protection' '${NGINX_X_XSS_PROTECTION}' always; + add_header 'X-Content-Type-Options' '${NGINX_X_CONTENT_TYPE_OPTIONS}' always; + } + + location ${NGINX_API_V1_LOCATION} { + proxy_pass http://apiv1; + proxy_set_header Host $host; + + # Custom nginx response headers + proxy_hide_header X-Robots-Tag; + add_header 'X-Robots-Tag' '${NGINX_API_X_ROBOTS_TAG}' always; + add_header 'X-XSS-Protection' '${NGINX_X_XSS_PROTECTION}' always; + add_header 'X-Content-Type-Options' '${NGINX_X_CONTENT_TYPE_OPTIONS}' always; + } + + location ~ /\.ht { + deny all; + } +} diff --git a/build/node20/nginx/etc/nginx/nginx.conf.template b/build/node20/nginx/etc/nginx/nginx.conf.template new file mode 100644 index 0000000..ae73604 --- /dev/null +++ b/build/node20/nginx/etc/nginx/nginx.conf.template @@ -0,0 +1,48 @@ +worker_processes ${NGINX_WORKER_PROCESSES}; +worker_rlimit_nofile ${NGINX_WORKER_RLIMIT_NOFILE}; + +error_log ${NGINX_ERROR_LOG}; +pid /run/nginx/nginx.pid; + +events { + worker_connections ${NGINX_WORKER_CONNECTIONS}; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + map $http_x_forwarded_proto $real_scheme { + default $http_x_forwarded_proto; + '' $scheme; + } + log_format main '$remote_addr - $remote_user [$time_local] "$request_method $real_scheme://$host$request_uri $server_protocol" $status ' + '$body_bytes_sent' + 'B ' + '$request_time' + 's ' + '"$http_referer" "$http_user_agent" "$http_x_forwarded_for" "$http_x_forwarded_proto"'; + client_body_temp_path /tmp/client_temp; + proxy_temp_path /tmp/proxy_temp_path; + fastcgi_temp_path /tmp/fastcgi_temp; + uwsgi_temp_path /tmp/uwsgi_temp; + scgi_temp_path /tmp/scgi_temp; + + map $status:$http_user_agent $loggable { + ~200:kube-probe ${NGINX_ACCESS_LOG_HEALTHCHECK_ENABLED}; + ~200:GoogleHC ${NGINX_ACCESS_LOG_HEALTHCHECK_ENABLED}; + default 1; + } + access_log ${NGINX_ACCESS_LOG} if=$loggable; + + server_tokens ${NGINX_SERVER_TOKENS}; + keepalive_timeout ${NGINX_KEEPALIVE_TIMEOUT}; + keepalive_requests ${NGINX_KEEPALIVE_REQUESTS}; + sendfile on; + + gzip on; + gzip_proxied any; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + + include /etc/nginx/conf.d/*.conf; +} diff --git a/build/node20/nginx/etc/supervisor/conf.d/nginx.conf b/build/node20/nginx/etc/supervisor/conf.d/nginx.conf new file mode 100644 index 0000000..5fe4411 --- /dev/null +++ b/build/node20/nginx/etc/supervisor/conf.d/nginx.conf @@ -0,0 +1,13 @@ +[program:nginx] +command=/usr/sbin/nginx -g "daemon off;" +autostart=true +autorestart=unexpected +# Expect 0 exit code returned when stopping the container +exitcodes=0 +priority=10 +stdout_events_enabled=true +stderr_events_enabled=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 diff --git a/build/node20/nginx/etc/supervisor/supervisord.conf b/build/node20/nginx/etc/supervisor/supervisord.conf new file mode 100644 index 0000000..dae2dc2 --- /dev/null +++ b/build/node20/nginx/etc/supervisor/supervisord.conf @@ -0,0 +1,23 @@ +[unix_http_server] +file=/var/run/supervisor/supervisor.sock +chmod=0700 +username=docker +password=docker + +[supervisorctl] +serverurl=unix:///var/run/supervisor/supervisor.sock +username=docker +password=docker + +[supervisord] +nodaemon=true +logfile=/dev/stdout +logfile_maxbytes=0 +pidfile=/var/run/supervisor/supervisord.pid +loglevel=info + +[rpcinterface:supervisor] +supervisor.rpcinterface_factory=supervisor.rpcinterface:make_main_rpcinterface + +[include] +files = /etc/supervisor/conf.d/*.conf diff --git a/build/node20/nginx/usr/local/bin/docker-custom-entrypoint b/build/node20/nginx/usr/local/bin/docker-custom-entrypoint new file mode 100755 index 0000000..12f7cdb --- /dev/null +++ b/build/node20/nginx/usr/local/bin/docker-custom-entrypoint @@ -0,0 +1,56 @@ +#!/bin/bash + +NGINX_CONF_TEMPLATE="/etc/nginx/nginx.conf.template" +NGINX_CONF="/etc/nginx/nginx.conf" +NGINX_DEFAULT_CONF_TEMPLATE="/etc/nginx/conf.d/default.conf.template" +NGINX_PROXY_CONF_TEMPLATE="/etc/nginx/conf.d/proxy.conf.template" +NGINX_DEFAULT_CONF="/etc/nginx/conf.d/default.conf" + +if [ -f ${NGINX_CONF_TEMPLATE} ]; then + echo "[INFO] Setup nginx \"${NGINX_CONF}\" config file" + envsubst "\ + \${NGINX_ACCESS_LOG_HEALTHCHECK_ENABLED} \ + \${NGINX_ACCESS_LOG} \ + \${NGINX_ERROR_LOG} \ + \${NGINX_KEEPALIVE_REQUESTS} \ + \${NGINX_KEEPALIVE_TIMEOUT} \ + \${NGINX_SERVER_TOKENS} \ + \${NGINX_WORKER_CONNECTIONS} \ + \${NGINX_WORKER_PROCESSES} \ + \${NGINX_WORKER_RLIMIT_NOFILE} \ + " <${NGINX_CONF_TEMPLATE} >${NGINX_CONF} +fi +if ${NGINX_PROXYPASS_CONFIG} && [ -f "${NGINX_PROXY_CONF_TEMPLATE}" ]; then + echo "[INFO] Setup nginx proxy \"${NGINX_DEFAULT_CONF}\" config file" + envsubst "\ + \${NGINX_API_LOCATION} \ + \${NGINX_API_V1_LOCATION} \ + \${NGINX_API_X_ROBOTS_TAG} \ + \${NGINX_CLIENT_MAX_BODY_SIZE} \ + \${NGINX_LARGE_CLIENT_HEADER_BUFFERS_SIZE} \ + \${NGINX_PORT} \ + \${NGINX_UPSTREAM_API_PORT} \ + \${NGINX_UPSTREAM_API_V1_PORT} \ + \${NGINX_UPSTREAM_WEBSOCKET_PORT} \ + \${NGINX_WEBSOCKET_LOCATION} \ + \${NGINX_WEBSOCKET_X_ROBOTS_TAG} \ + \${NGINX_X_CONTENT_TYPE_OPTIONS} \ + \${NGINX_X_XSS_PROTECTION} \ + " <${NGINX_PROXY_CONF_TEMPLATE} >${NGINX_DEFAULT_CONF} +elif [ -f "${NGINX_DEFAULT_CONF_TEMPLATE}" ]; then + echo "[INFO] Setup nginx default \"${NGINX_DEFAULT_CONF}\" config file" + envsubst "\ + \${NGINX_CLIENT_MAX_BODY_SIZE} \ + \${NGINX_DOCUMENTS_X_ROBOTS_TAG} \ + \${NGINX_HTML_X_ROBOTS_TAG} \ + \${NGINX_LARGE_CLIENT_HEADER_BUFFERS_SIZE} \ + \${NGINX_PORT} \ + \${NGINX_ROOT} \ + \${NGINX_STATIC_CACHE_CONTROL_HEADER} \ + \${NGINX_STATIC_X_CACHE_CONTROL_TTL_HEADER} \ + \${NGINX_STATIC_X_ROBOTS_TAG} \ + \${NGINX_X_CONTENT_TYPE_OPTIONS} \ + \${NGINX_X_XSS_PROTECTION} \ + " <${NGINX_DEFAULT_CONF_TEMPLATE} >${NGINX_DEFAULT_CONF} +fi +exec "$@" diff --git a/build/node20/nginx/usr/local/bin/env-config b/build/node20/nginx/usr/local/bin/env-config new file mode 100755 index 0000000..36ce7d3 --- /dev/null +++ b/build/node20/nginx/usr/local/bin/env-config @@ -0,0 +1,178 @@ +#!/bin/bash +# Script used to run the setup of config.json file using .env files and exported variables: +# - if the variable is exported in system, then it is left untouched and not exported from any env file +# - all missing variables are exported from main env file if exists +# - all missing variables (the same as from main env file) are exported from local env file if exists +set -euo pipefail + +CONFIG_DIST_PATH="public/config.json.dist" +CONFIG_PATH="public/config.json" +DEBUG=false +END_FLOW=false +ENV_APPLICATION_FILE_DIST=".env" +ENV_APPLICATION_FILE=".env.local" +PROJECT_ROOT="/var/www/html" + +function echo_debug() { + if ${DEBUG}; then + echo -e "[DEBUG] $*" + fi +} + +function help() { + echo -en "$(printf %q "${BASH_SOURCE[0]}") [OPERATION] [OPTIONS]... + \nBash script to run env config.json setup inside the container. + + Options: + --config-dist-path (Optional) Path to existing config.json.dist file + Default: public/config.json.dist + --config-path (Optional) Path to config.json file to be created + Default: public/config.json + --debug (Optional) Whether to enable debug output + --env-dist-file (Optional) Path to existing .env file + Default: .env + --env-file (Optional) Path to .env.local file to be created + Default: .env.local + --project-root (Optional) Path project home where to find config and env files + Default: /var/www/html + + Examples: + $(printf %q "${BASH_SOURCE[0]}") --config-dist-path dist/config.json.dist --config-path dist/config.json + \n" +} + +function set_vars_from_file() { + if [ -z "${MISSING_VARIABLE_NAMES}" ]; then + echo_debug "All variables are exported, there is nothing needed to export from $1 file" + return + fi + while IFS= read -r variable; do + variable_name=$(echo "${variable?}" | cut -d "=" -f 1) + # Export only missing variables + if echo "${MISSING_VARIABLE_NAMES}" | grep -q -w "$variable_name"; then + ENV_VARS+=("${variable?}") + echo_debug "Preparing variable for export: ${variable?}" + fi + done < <(grep -v "^#" "$1" | sed '/^$/d') + echo_debug "Prepared variables for export:" "${ENV_VARS[@]}" + for var in "${ENV_VARS[@]}"; do export "${var?}"; done +} + +while [ "$#" -gt 0 ]; do + case $1 in + --config-path) + CONFIG_PATH=$2 + shift 2 + continue + ;; + --config-path=*) + CONFIG_PATH=${1#*=} + shift 1 + continue + ;; + --config-dist-path) + CONFIG_DIST_PATH=$2 + shift 2 + continue + ;; + --config-dist-path=*) + CONFIG_DIST_PATH=${1#*=} + shift 1 + continue + ;; + --debug) + DEBUG=true + shift 1 + continue + ;; + --env-file) + ENV_APPLICATION_FILE=$2 + shift 2 + continue + ;; + --env-file=*) + ENV_APPLICATION_FILE=${1#*=} + shift 1 + continue + ;; + --env-dist-file) + ENV_APPLICATION_FILE_DIST=$2 + shift 2 + continue + ;; + --env-dist-file=*) + ENV_APPLICATION_FILE_DIST=${1#*=} + shift 1 + continue + ;; + --project-root) + PROJECT_ROOT=$2 + shift 2 + continue + ;; + --project-root=*) + PROJECT_ROOT=${1#*=} + shift 1 + continue + ;; + -h | --help) + help + exit + ;; + *) + echo "[WARN] Unknown command line switch: $1" >&2 + help + exit + ;; + esac +done + +if [ ! -f "${CONFIG_DIST_PATH}" ]; then + echo "[INFO] Project root: ${PROJECT_ROOT}" + echo "[ERROR] File ${CONFIG_DIST_PATH} does not exist, provide correct path using --config-dist-path option" + END_FLOW=true +fi + +if [ ! -f "${ENV_APPLICATION_FILE_DIST}" ]; then + echo "[INFO] Project root: ${PROJECT_ROOT}" + echo "[ERROR] File ${ENV_APPLICATION_FILE_DIST} does not exist, provide correct path using --env-dist-path option" + END_FLOW=true +fi + +if ${END_FLOW}; then + echo -e "\n" + help + exit 1 +fi + +cd "${PROJECT_ROOT}" || exit 1 + +# Get all variables from main env file +ALL_VARIABLE_NAMES=$(grep -v "^#" "${ENV_APPLICATION_FILE_DIST}" | sed -e '/^$/d' | cut -d "=" -f 1) +# Set missing variables to all variables, we will filter them later in the script +MISSING_VARIABLE_NAMES=${ALL_VARIABLE_NAMES} + +# Remove already exported (existing) variables from missing variables to make the list clear +for variable_name in ${ALL_VARIABLE_NAMES}; do + if [ -n "${!variable_name+x}" ]; then + MISSING_VARIABLE_NAMES=${MISSING_VARIABLE_NAMES//$variable_name/} + echo_debug "Variable already exported: ${variable_name?}" + fi +done +MISSING_VARIABLE_NAMES="$(echo "${MISSING_VARIABLE_NAMES}" | sed '/^$/d' | tr '\n' ' ')" +echo_debug "Variables to read from env files: ${MISSING_VARIABLE_NAMES}" + +# Export missing variables from main env file if exists +if [ -f "${ENV_APPLICATION_FILE_DIST}" ]; then + echo_debug "Reading variables from ${ENV_APPLICATION_FILE_DIST} file" + set_vars_from_file "${ENV_APPLICATION_FILE_DIST}" +fi + +# Export missing variables from local env file if exists +if [ -f "${ENV_APPLICATION_FILE}" ]; then + echo_debug "Reading variables from ${ENV_APPLICATION_FILE} file" + set_vars_from_file "${ENV_APPLICATION_FILE}" +fi + +mkdir -p "$(dirname "${CONFIG_PATH}")" +envsubst <"${CONFIG_DIST_PATH}" >"${CONFIG_PATH}" diff --git a/build/node20/nginx/usr/local/bin/fix-user b/build/node20/nginx/usr/local/bin/fix-user new file mode 100755 index 0000000..fafd384 --- /dev/null +++ b/build/node20/nginx/usr/local/bin/fix-user @@ -0,0 +1,97 @@ +#!/bin/bash +# +# Corrects UID and GID for container user according to host UID and GID if needed +# +# #1 Example: UID 1000 and GID 1000: +# ./fix-user test user 1000 1000 +# - 'user' has UID 1000 and GID 1000 by default - nothing will happen +# +# #2 Example: UID 501 (not exists in docker image) and GID 501 (not exists in docker image): +# ./fix-user test user 501 501 +# - 'user' UID will change to 501 +# - 'user' GID will change to 501 +# +# #3 Example: UID 35 (exists in docker image as 'games') and GID 100 (exists in docker image as 'users'): +# ./fix-user test user 35 100 +# - 'games' UID will change to random free UID (1100-2000) to release UID 35 +# - 'user' UID will change to 35 +# - adds user 'user' to group 'users' + +HOST_USER=$1 +CONTAINER_USER_NAME=$2 +HOST_USER_ID=$3 +HOST_GROUP_ID=$4 +# User name which exists under hosts user id +EXISTING_CONTAINER_USER_NAME=$(getent passwd "${HOST_USER_ID}" | cut -d: -f1) +EXISTING_CONTAINER_NEW_USER_ID="" +# Group name which exists under hosts group id +EXISTING_CONTAINER_GROUP_NAME=$(getent group "${HOST_GROUP_ID}" | cut -d: -f1) +# User ID of the container user +CONTAINER_USER_ID=$(id -u "${CONTAINER_USER_NAME}") +# Group ID and group name of the container user +CONTAINER_GROUP_ID=$(id -g "${CONTAINER_USER_NAME}") +CONTAINER_GROUP_NAME=$(getent group "${CONTAINER_GROUP_ID}" | cut -d: -f1) +FINAL_GROUP_NAME=${EXISTING_CONTAINER_GROUP_NAME} +SYSTEM_FOLDERS=" \ + ${COREPACK_HOME} \ + ${YARN_CACHE_FOLDER} \ + /etc/nginx \ + /home/node \ + /run/nginx \ + /run/secrets \ + /usr/local/lib/node_modules \ + /usr/local/log \ + /var/log/cron \ + /var/log/nginx \ + /var/run/supervisor \ + /var/www/html \ +" + +# Skip this script if the host user is root +if [ "$HOST_USER" == "root" ]; then + exit 0 +fi + +# Final group name to be used will be container group if no other group exists with the host group ID +if [ -z "${FINAL_GROUP_NAME}" ]; then + FINAL_GROUP_NAME=${CONTAINER_GROUP_NAME} +fi + +# Generate new user ID to be used for existing container user +while [ -n "$(getent passwd "${EXISTING_CONTAINER_NEW_USER_ID}")" ]; do + EXISTING_CONTAINER_NEW_USER_ID=$(shuf -i 1100-2000 -n 1) +done + +# Change user ID for container user if needed +if [ -z "${EXISTING_CONTAINER_USER_NAME}" ] && [ ! "${CONTAINER_USER_ID}" == "${HOST_USER_ID}" ]; then + echo "Changing '${CONTAINER_USER_NAME}' user ID ${CONTAINER_USER_ID} to ${HOST_USER_ID}" + usermod -u "${HOST_USER_ID}" "${CONTAINER_USER_NAME}" +fi + +# Change user ID for existing container user and container user if needed +if [ -n "${EXISTING_CONTAINER_USER_NAME}" ] && [ ! "${EXISTING_CONTAINER_USER_NAME}" == "${CONTAINER_USER_NAME}" ]; then + echo "Changing '${EXISTING_CONTAINER_USER_NAME}' user ID ${HOST_USER_ID} to ${EXISTING_CONTAINER_NEW_USER_ID}" + usermod -u "${EXISTING_CONTAINER_NEW_USER_ID}" "${EXISTING_CONTAINER_USER_NAME}" 2>&1 + echo "Changing '${CONTAINER_USER_NAME}' user ID ${CONTAINER_USER_ID} to ${HOST_USER_ID}" + usermod -u "${HOST_USER_ID}" "${CONTAINER_USER_NAME}" +fi + +# Change group ID for container group name if needed +if [ "${FINAL_GROUP_NAME}" == "${CONTAINER_GROUP_NAME}" ] && [ ! "${CONTAINER_GROUP_ID}" == "${HOST_GROUP_ID}" ]; then + echo "Changing '${FINAL_GROUP_NAME}' group ID ${CONTAINER_GROUP_ID} to ${HOST_GROUP_ID}" + groupmod -g "${HOST_GROUP_ID}" "${FINAL_GROUP_NAME}" + find / -group "${CONTAINER_GROUP_ID}" -exec chgrp -h "${HOST_GROUP_ID}" {} \; +fi + +# Assign correct group for existing container user name if needed +if [ ! "${FINAL_GROUP_NAME}" == "${CONTAINER_GROUP_NAME}" ]; then + echo "Adding user with UID ${HOST_USER_ID} (${CONTAINER_USER_NAME}) to group with GID ${HOST_GROUP_ID} (${FINAL_GROUP_NAME})" + usermod -a -G "${FINAL_GROUP_NAME}" "${CONTAINER_USER_NAME}" 2>&1 +fi + +echo "Setting up system user permissions (${CONTAINER_USER_NAME}:${FINAL_GROUP_NAME})" +for folder in ${SYSTEM_FOLDERS}; do + if [ -d "$folder" ]; then + chown "${CONTAINER_USER_NAME}":"${FINAL_GROUP_NAME}" -R "$folder" + fi +done diff --git a/config/all/etc/supervisor/supervisord.conf b/config/all/etc/supervisor/supervisord.conf new file mode 100644 index 0000000..dae2dc2 --- /dev/null +++ b/config/all/etc/supervisor/supervisord.conf @@ -0,0 +1,23 @@ +[unix_http_server] +file=/var/run/supervisor/supervisor.sock +chmod=0700 +username=docker +password=docker + +[supervisorctl] +serverurl=unix:///var/run/supervisor/supervisor.sock +username=docker +password=docker + +[supervisord] +nodaemon=true +logfile=/dev/stdout +logfile_maxbytes=0 +pidfile=/var/run/supervisor/supervisord.pid +loglevel=info + +[rpcinterface:supervisor] +supervisor.rpcinterface_factory=supervisor.rpcinterface:make_main_rpcinterface + +[include] +files = /etc/supervisor/conf.d/*.conf diff --git a/config/all/usr/local/bin/docker-custom-entrypoint b/config/all/usr/local/bin/docker-custom-entrypoint new file mode 100755 index 0000000..5fc4448 --- /dev/null +++ b/config/all/usr/local/bin/docker-custom-entrypoint @@ -0,0 +1,3 @@ +#!/bin/bash + +exec "$@" diff --git a/config/all/usr/local/bin/env-config b/config/all/usr/local/bin/env-config new file mode 100755 index 0000000..36ce7d3 --- /dev/null +++ b/config/all/usr/local/bin/env-config @@ -0,0 +1,178 @@ +#!/bin/bash +# Script used to run the setup of config.json file using .env files and exported variables: +# - if the variable is exported in system, then it is left untouched and not exported from any env file +# - all missing variables are exported from main env file if exists +# - all missing variables (the same as from main env file) are exported from local env file if exists +set -euo pipefail + +CONFIG_DIST_PATH="public/config.json.dist" +CONFIG_PATH="public/config.json" +DEBUG=false +END_FLOW=false +ENV_APPLICATION_FILE_DIST=".env" +ENV_APPLICATION_FILE=".env.local" +PROJECT_ROOT="/var/www/html" + +function echo_debug() { + if ${DEBUG}; then + echo -e "[DEBUG] $*" + fi +} + +function help() { + echo -en "$(printf %q "${BASH_SOURCE[0]}") [OPERATION] [OPTIONS]... + \nBash script to run env config.json setup inside the container. + + Options: + --config-dist-path (Optional) Path to existing config.json.dist file + Default: public/config.json.dist + --config-path (Optional) Path to config.json file to be created + Default: public/config.json + --debug (Optional) Whether to enable debug output + --env-dist-file (Optional) Path to existing .env file + Default: .env + --env-file (Optional) Path to .env.local file to be created + Default: .env.local + --project-root (Optional) Path project home where to find config and env files + Default: /var/www/html + + Examples: + $(printf %q "${BASH_SOURCE[0]}") --config-dist-path dist/config.json.dist --config-path dist/config.json + \n" +} + +function set_vars_from_file() { + if [ -z "${MISSING_VARIABLE_NAMES}" ]; then + echo_debug "All variables are exported, there is nothing needed to export from $1 file" + return + fi + while IFS= read -r variable; do + variable_name=$(echo "${variable?}" | cut -d "=" -f 1) + # Export only missing variables + if echo "${MISSING_VARIABLE_NAMES}" | grep -q -w "$variable_name"; then + ENV_VARS+=("${variable?}") + echo_debug "Preparing variable for export: ${variable?}" + fi + done < <(grep -v "^#" "$1" | sed '/^$/d') + echo_debug "Prepared variables for export:" "${ENV_VARS[@]}" + for var in "${ENV_VARS[@]}"; do export "${var?}"; done +} + +while [ "$#" -gt 0 ]; do + case $1 in + --config-path) + CONFIG_PATH=$2 + shift 2 + continue + ;; + --config-path=*) + CONFIG_PATH=${1#*=} + shift 1 + continue + ;; + --config-dist-path) + CONFIG_DIST_PATH=$2 + shift 2 + continue + ;; + --config-dist-path=*) + CONFIG_DIST_PATH=${1#*=} + shift 1 + continue + ;; + --debug) + DEBUG=true + shift 1 + continue + ;; + --env-file) + ENV_APPLICATION_FILE=$2 + shift 2 + continue + ;; + --env-file=*) + ENV_APPLICATION_FILE=${1#*=} + shift 1 + continue + ;; + --env-dist-file) + ENV_APPLICATION_FILE_DIST=$2 + shift 2 + continue + ;; + --env-dist-file=*) + ENV_APPLICATION_FILE_DIST=${1#*=} + shift 1 + continue + ;; + --project-root) + PROJECT_ROOT=$2 + shift 2 + continue + ;; + --project-root=*) + PROJECT_ROOT=${1#*=} + shift 1 + continue + ;; + -h | --help) + help + exit + ;; + *) + echo "[WARN] Unknown command line switch: $1" >&2 + help + exit + ;; + esac +done + +if [ ! -f "${CONFIG_DIST_PATH}" ]; then + echo "[INFO] Project root: ${PROJECT_ROOT}" + echo "[ERROR] File ${CONFIG_DIST_PATH} does not exist, provide correct path using --config-dist-path option" + END_FLOW=true +fi + +if [ ! -f "${ENV_APPLICATION_FILE_DIST}" ]; then + echo "[INFO] Project root: ${PROJECT_ROOT}" + echo "[ERROR] File ${ENV_APPLICATION_FILE_DIST} does not exist, provide correct path using --env-dist-path option" + END_FLOW=true +fi + +if ${END_FLOW}; then + echo -e "\n" + help + exit 1 +fi + +cd "${PROJECT_ROOT}" || exit 1 + +# Get all variables from main env file +ALL_VARIABLE_NAMES=$(grep -v "^#" "${ENV_APPLICATION_FILE_DIST}" | sed -e '/^$/d' | cut -d "=" -f 1) +# Set missing variables to all variables, we will filter them later in the script +MISSING_VARIABLE_NAMES=${ALL_VARIABLE_NAMES} + +# Remove already exported (existing) variables from missing variables to make the list clear +for variable_name in ${ALL_VARIABLE_NAMES}; do + if [ -n "${!variable_name+x}" ]; then + MISSING_VARIABLE_NAMES=${MISSING_VARIABLE_NAMES//$variable_name/} + echo_debug "Variable already exported: ${variable_name?}" + fi +done +MISSING_VARIABLE_NAMES="$(echo "${MISSING_VARIABLE_NAMES}" | sed '/^$/d' | tr '\n' ' ')" +echo_debug "Variables to read from env files: ${MISSING_VARIABLE_NAMES}" + +# Export missing variables from main env file if exists +if [ -f "${ENV_APPLICATION_FILE_DIST}" ]; then + echo_debug "Reading variables from ${ENV_APPLICATION_FILE_DIST} file" + set_vars_from_file "${ENV_APPLICATION_FILE_DIST}" +fi + +# Export missing variables from local env file if exists +if [ -f "${ENV_APPLICATION_FILE}" ]; then + echo_debug "Reading variables from ${ENV_APPLICATION_FILE} file" + set_vars_from_file "${ENV_APPLICATION_FILE}" +fi + +mkdir -p "$(dirname "${CONFIG_PATH}")" +envsubst <"${CONFIG_DIST_PATH}" >"${CONFIG_PATH}" diff --git a/config/all/usr/local/bin/fix-user b/config/all/usr/local/bin/fix-user new file mode 100755 index 0000000..fafd384 --- /dev/null +++ b/config/all/usr/local/bin/fix-user @@ -0,0 +1,97 @@ +#!/bin/bash +# +# Corrects UID and GID for container user according to host UID and GID if needed +# +# #1 Example: UID 1000 and GID 1000: +# ./fix-user test user 1000 1000 +# - 'user' has UID 1000 and GID 1000 by default - nothing will happen +# +# #2 Example: UID 501 (not exists in docker image) and GID 501 (not exists in docker image): +# ./fix-user test user 501 501 +# - 'user' UID will change to 501 +# - 'user' GID will change to 501 +# +# #3 Example: UID 35 (exists in docker image as 'games') and GID 100 (exists in docker image as 'users'): +# ./fix-user test user 35 100 +# - 'games' UID will change to random free UID (1100-2000) to release UID 35 +# - 'user' UID will change to 35 +# - adds user 'user' to group 'users' + +HOST_USER=$1 +CONTAINER_USER_NAME=$2 +HOST_USER_ID=$3 +HOST_GROUP_ID=$4 +# User name which exists under hosts user id +EXISTING_CONTAINER_USER_NAME=$(getent passwd "${HOST_USER_ID}" | cut -d: -f1) +EXISTING_CONTAINER_NEW_USER_ID="" +# Group name which exists under hosts group id +EXISTING_CONTAINER_GROUP_NAME=$(getent group "${HOST_GROUP_ID}" | cut -d: -f1) +# User ID of the container user +CONTAINER_USER_ID=$(id -u "${CONTAINER_USER_NAME}") +# Group ID and group name of the container user +CONTAINER_GROUP_ID=$(id -g "${CONTAINER_USER_NAME}") +CONTAINER_GROUP_NAME=$(getent group "${CONTAINER_GROUP_ID}" | cut -d: -f1) +FINAL_GROUP_NAME=${EXISTING_CONTAINER_GROUP_NAME} +SYSTEM_FOLDERS=" \ + ${COREPACK_HOME} \ + ${YARN_CACHE_FOLDER} \ + /etc/nginx \ + /home/node \ + /run/nginx \ + /run/secrets \ + /usr/local/lib/node_modules \ + /usr/local/log \ + /var/log/cron \ + /var/log/nginx \ + /var/run/supervisor \ + /var/www/html \ +" + +# Skip this script if the host user is root +if [ "$HOST_USER" == "root" ]; then + exit 0 +fi + +# Final group name to be used will be container group if no other group exists with the host group ID +if [ -z "${FINAL_GROUP_NAME}" ]; then + FINAL_GROUP_NAME=${CONTAINER_GROUP_NAME} +fi + +# Generate new user ID to be used for existing container user +while [ -n "$(getent passwd "${EXISTING_CONTAINER_NEW_USER_ID}")" ]; do + EXISTING_CONTAINER_NEW_USER_ID=$(shuf -i 1100-2000 -n 1) +done + +# Change user ID for container user if needed +if [ -z "${EXISTING_CONTAINER_USER_NAME}" ] && [ ! "${CONTAINER_USER_ID}" == "${HOST_USER_ID}" ]; then + echo "Changing '${CONTAINER_USER_NAME}' user ID ${CONTAINER_USER_ID} to ${HOST_USER_ID}" + usermod -u "${HOST_USER_ID}" "${CONTAINER_USER_NAME}" +fi + +# Change user ID for existing container user and container user if needed +if [ -n "${EXISTING_CONTAINER_USER_NAME}" ] && [ ! "${EXISTING_CONTAINER_USER_NAME}" == "${CONTAINER_USER_NAME}" ]; then + echo "Changing '${EXISTING_CONTAINER_USER_NAME}' user ID ${HOST_USER_ID} to ${EXISTING_CONTAINER_NEW_USER_ID}" + usermod -u "${EXISTING_CONTAINER_NEW_USER_ID}" "${EXISTING_CONTAINER_USER_NAME}" 2>&1 + echo "Changing '${CONTAINER_USER_NAME}' user ID ${CONTAINER_USER_ID} to ${HOST_USER_ID}" + usermod -u "${HOST_USER_ID}" "${CONTAINER_USER_NAME}" +fi + +# Change group ID for container group name if needed +if [ "${FINAL_GROUP_NAME}" == "${CONTAINER_GROUP_NAME}" ] && [ ! "${CONTAINER_GROUP_ID}" == "${HOST_GROUP_ID}" ]; then + echo "Changing '${FINAL_GROUP_NAME}' group ID ${CONTAINER_GROUP_ID} to ${HOST_GROUP_ID}" + groupmod -g "${HOST_GROUP_ID}" "${FINAL_GROUP_NAME}" + find / -group "${CONTAINER_GROUP_ID}" -exec chgrp -h "${HOST_GROUP_ID}" {} \; +fi + +# Assign correct group for existing container user name if needed +if [ ! "${FINAL_GROUP_NAME}" == "${CONTAINER_GROUP_NAME}" ]; then + echo "Adding user with UID ${HOST_USER_ID} (${CONTAINER_USER_NAME}) to group with GID ${HOST_GROUP_ID} (${FINAL_GROUP_NAME})" + usermod -a -G "${FINAL_GROUP_NAME}" "${CONTAINER_USER_NAME}" 2>&1 +fi + +echo "Setting up system user permissions (${CONTAINER_USER_NAME}:${FINAL_GROUP_NAME})" +for folder in ${SYSTEM_FOLDERS}; do + if [ -d "$folder" ]; then + chown "${CONTAINER_USER_NAME}":"${FINAL_GROUP_NAME}" -R "$folder" + fi +done diff --git a/config/browsers/etc/fonts/local.conf b/config/browsers/etc/fonts/local.conf new file mode 100644 index 0000000..07f242c --- /dev/null +++ b/config/browsers/etc/fonts/local.conf @@ -0,0 +1,34 @@ + + + + + +rgb + + + + +true + + + + +hintslight + + + + +true + + + + +lcddefault + + + + +false + + + \ No newline at end of file diff --git a/config/browsers/usr/share/dbus-1/system.conf b/config/browsers/usr/share/dbus-1/system.conf new file mode 100644 index 0000000..614ca75 --- /dev/null +++ b/config/browsers/usr/share/dbus-1/system.conf @@ -0,0 +1,141 @@ + + + + + + + + + system + + + node + + + + + + + + + /usr/lib/dbus-1.0/dbus-daemon-launch-helper + + + /run/dbus/pid + + + + + + EXTERNAL + + + unix:path=/run/dbus/system_bus_socket + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /etc/dbus-1/system.conf.dpkg-bak + + + + + + + + + + + + + + + + + + + + + system.d + + /etc/dbus-1/system.d + + + /etc/dbus-1/system-local.conf + + contexts/dbus_contexts + + \ No newline at end of file diff --git a/config/nginx/etc/nginx/conf.d/default.conf.template b/config/nginx/etc/nginx/conf.d/default.conf.template new file mode 100644 index 0000000..6dbca91 --- /dev/null +++ b/config/nginx/etc/nginx/conf.d/default.conf.template @@ -0,0 +1,58 @@ +map $status $static_cache_control_header { + 404 "no-cache, no-store, must-revalidate"; + default "${NGINX_STATIC_CACHE_CONTROL_HEADER}"; +} + +server { + listen ${NGINX_PORT}; + + root ${NGINX_ROOT}; + index index.html; + client_max_body_size ${NGINX_CLIENT_MAX_BODY_SIZE}; + large_client_header_buffers 4 ${NGINX_LARGE_CLIENT_HEADER_BUFFERS_SIZE}; + + #App paths + location / { + try_files $uri $uri/ @rewrites; + } + + location @rewrites { + rewrite ^(.+)$ /index.html last; + } + + location ~* \.html?$ { + expires -1; + add_header Pragma "no-cache"; + add_header Cache-Control "no-store, must-revalidate"; + add_header Host $host; + + # Custom nginx response headers + add_header 'X-Robots-Tag' '${NGINX_HTML_X_ROBOTS_TAG}' always; + add_header 'X-XSS-Protection' '${NGINX_X_XSS_PROTECTION}' always; + add_header 'X-Content-Type-Options' '${NGINX_X_CONTENT_TYPE_OPTIONS}' always; + } + + location ~* ^[^\?\&]+\.(json|zip|tgz|gz|rar|bz2|doc|xls|pdf|ppt|txt|tar|rtf|mid|midi|wav)$ { + add_header Host $host; + + # Custom nginx response headers + add_header 'X-Robots-Tag' '${NGINX_DOCUMENTS_X_ROBOTS_TAG}' always; + add_header 'X-XSS-Protection' '${NGINX_X_XSS_PROTECTION}' always; + add_header 'X-Content-Type-Options' '${NGINX_X_CONTENT_TYPE_OPTIONS}' always; + } + + location ~* ^[^\?\&]+\.(jpg|jpeg|gif|png|ico|css|js|svg|bmp|eot|woff|woff2|ttf)$ { + add_header Host $host; + add_header Cache-Control $static_cache_control_header always; + add_header X-Cache-Control-TTL ${NGINX_STATIC_X_CACHE_CONTROL_TTL_HEADER}; + + # Custom nginx response headers + add_header 'X-Robots-Tag' '${NGINX_STATIC_X_ROBOTS_TAG}' always; + add_header 'X-XSS-Protection' '${NGINX_X_XSS_PROTECTION}' always; + add_header 'X-Content-Type-Options' '${NGINX_X_CONTENT_TYPE_OPTIONS}' always; + } + + location ~ /\.ht { + deny all; + } +} diff --git a/config/nginx/etc/nginx/conf.d/proxy.conf.template b/config/nginx/etc/nginx/conf.d/proxy.conf.template new file mode 100644 index 0000000..4f5b34d --- /dev/null +++ b/config/nginx/etc/nginx/conf.d/proxy.conf.template @@ -0,0 +1,65 @@ +map $http_upgrade $connection_upgrade { + '' close; + default upgrade; +} + +upstream websocket { + server 127.0.0.1:${NGINX_UPSTREAM_WEBSOCKET_PORT}; +} + +upstream api { + server 127.0.0.1:${NGINX_UPSTREAM_API_PORT}; +} + +upstream apiv1 { + server 127.0.0.1:${NGINX_UPSTREAM_API_V1_PORT}; +} + +server { + listen ${NGINX_PORT}; + + sendfile off; + client_max_body_size ${NGINX_CLIENT_MAX_BODY_SIZE}; + large_client_header_buffers 4 ${NGINX_LARGE_CLIENT_HEADER_BUFFERS_SIZE}; + + #App paths + location ${NGINX_WEBSOCKET_LOCATION} { + proxy_pass http://websocket; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $host; + + # Custom nginx response headers + proxy_hide_header X-Robots-Tag; + add_header 'X-Robots-Tag' '${NGINX_WEBSOCKET_X_ROBOTS_TAG}' always; + add_header 'X-XSS-Protection' '${NGINX_X_XSS_PROTECTION}' always; + add_header 'X-Content-Type-Options' '${NGINX_X_CONTENT_TYPE_OPTIONS}' always; + } + + location ${NGINX_API_LOCATION} { + proxy_pass http://api; + proxy_set_header Host $host; + + # Custom nginx response headers + proxy_hide_header X-Robots-Tag; + add_header 'X-Robots-Tag' '${NGINX_API_X_ROBOTS_TAG}' always; + add_header 'X-XSS-Protection' '${NGINX_X_XSS_PROTECTION}' always; + add_header 'X-Content-Type-Options' '${NGINX_X_CONTENT_TYPE_OPTIONS}' always; + } + + location ${NGINX_API_V1_LOCATION} { + proxy_pass http://apiv1; + proxy_set_header Host $host; + + # Custom nginx response headers + proxy_hide_header X-Robots-Tag; + add_header 'X-Robots-Tag' '${NGINX_API_X_ROBOTS_TAG}' always; + add_header 'X-XSS-Protection' '${NGINX_X_XSS_PROTECTION}' always; + add_header 'X-Content-Type-Options' '${NGINX_X_CONTENT_TYPE_OPTIONS}' always; + } + + location ~ /\.ht { + deny all; + } +} diff --git a/config/nginx/etc/nginx/nginx.conf.template b/config/nginx/etc/nginx/nginx.conf.template new file mode 100644 index 0000000..ae73604 --- /dev/null +++ b/config/nginx/etc/nginx/nginx.conf.template @@ -0,0 +1,48 @@ +worker_processes ${NGINX_WORKER_PROCESSES}; +worker_rlimit_nofile ${NGINX_WORKER_RLIMIT_NOFILE}; + +error_log ${NGINX_ERROR_LOG}; +pid /run/nginx/nginx.pid; + +events { + worker_connections ${NGINX_WORKER_CONNECTIONS}; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + map $http_x_forwarded_proto $real_scheme { + default $http_x_forwarded_proto; + '' $scheme; + } + log_format main '$remote_addr - $remote_user [$time_local] "$request_method $real_scheme://$host$request_uri $server_protocol" $status ' + '$body_bytes_sent' + 'B ' + '$request_time' + 's ' + '"$http_referer" "$http_user_agent" "$http_x_forwarded_for" "$http_x_forwarded_proto"'; + client_body_temp_path /tmp/client_temp; + proxy_temp_path /tmp/proxy_temp_path; + fastcgi_temp_path /tmp/fastcgi_temp; + uwsgi_temp_path /tmp/uwsgi_temp; + scgi_temp_path /tmp/scgi_temp; + + map $status:$http_user_agent $loggable { + ~200:kube-probe ${NGINX_ACCESS_LOG_HEALTHCHECK_ENABLED}; + ~200:GoogleHC ${NGINX_ACCESS_LOG_HEALTHCHECK_ENABLED}; + default 1; + } + access_log ${NGINX_ACCESS_LOG} if=$loggable; + + server_tokens ${NGINX_SERVER_TOKENS}; + keepalive_timeout ${NGINX_KEEPALIVE_TIMEOUT}; + keepalive_requests ${NGINX_KEEPALIVE_REQUESTS}; + sendfile on; + + gzip on; + gzip_proxied any; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + + include /etc/nginx/conf.d/*.conf; +} diff --git a/config/nginx/etc/supervisor/conf.d/nginx.conf b/config/nginx/etc/supervisor/conf.d/nginx.conf new file mode 100644 index 0000000..5fe4411 --- /dev/null +++ b/config/nginx/etc/supervisor/conf.d/nginx.conf @@ -0,0 +1,13 @@ +[program:nginx] +command=/usr/sbin/nginx -g "daemon off;" +autostart=true +autorestart=unexpected +# Expect 0 exit code returned when stopping the container +exitcodes=0 +priority=10 +stdout_events_enabled=true +stderr_events_enabled=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 diff --git a/config/nginx/usr/local/bin/docker-custom-entrypoint b/config/nginx/usr/local/bin/docker-custom-entrypoint new file mode 100755 index 0000000..12f7cdb --- /dev/null +++ b/config/nginx/usr/local/bin/docker-custom-entrypoint @@ -0,0 +1,56 @@ +#!/bin/bash + +NGINX_CONF_TEMPLATE="/etc/nginx/nginx.conf.template" +NGINX_CONF="/etc/nginx/nginx.conf" +NGINX_DEFAULT_CONF_TEMPLATE="/etc/nginx/conf.d/default.conf.template" +NGINX_PROXY_CONF_TEMPLATE="/etc/nginx/conf.d/proxy.conf.template" +NGINX_DEFAULT_CONF="/etc/nginx/conf.d/default.conf" + +if [ -f ${NGINX_CONF_TEMPLATE} ]; then + echo "[INFO] Setup nginx \"${NGINX_CONF}\" config file" + envsubst "\ + \${NGINX_ACCESS_LOG_HEALTHCHECK_ENABLED} \ + \${NGINX_ACCESS_LOG} \ + \${NGINX_ERROR_LOG} \ + \${NGINX_KEEPALIVE_REQUESTS} \ + \${NGINX_KEEPALIVE_TIMEOUT} \ + \${NGINX_SERVER_TOKENS} \ + \${NGINX_WORKER_CONNECTIONS} \ + \${NGINX_WORKER_PROCESSES} \ + \${NGINX_WORKER_RLIMIT_NOFILE} \ + " <${NGINX_CONF_TEMPLATE} >${NGINX_CONF} +fi +if ${NGINX_PROXYPASS_CONFIG} && [ -f "${NGINX_PROXY_CONF_TEMPLATE}" ]; then + echo "[INFO] Setup nginx proxy \"${NGINX_DEFAULT_CONF}\" config file" + envsubst "\ + \${NGINX_API_LOCATION} \ + \${NGINX_API_V1_LOCATION} \ + \${NGINX_API_X_ROBOTS_TAG} \ + \${NGINX_CLIENT_MAX_BODY_SIZE} \ + \${NGINX_LARGE_CLIENT_HEADER_BUFFERS_SIZE} \ + \${NGINX_PORT} \ + \${NGINX_UPSTREAM_API_PORT} \ + \${NGINX_UPSTREAM_API_V1_PORT} \ + \${NGINX_UPSTREAM_WEBSOCKET_PORT} \ + \${NGINX_WEBSOCKET_LOCATION} \ + \${NGINX_WEBSOCKET_X_ROBOTS_TAG} \ + \${NGINX_X_CONTENT_TYPE_OPTIONS} \ + \${NGINX_X_XSS_PROTECTION} \ + " <${NGINX_PROXY_CONF_TEMPLATE} >${NGINX_DEFAULT_CONF} +elif [ -f "${NGINX_DEFAULT_CONF_TEMPLATE}" ]; then + echo "[INFO] Setup nginx default \"${NGINX_DEFAULT_CONF}\" config file" + envsubst "\ + \${NGINX_CLIENT_MAX_BODY_SIZE} \ + \${NGINX_DOCUMENTS_X_ROBOTS_TAG} \ + \${NGINX_HTML_X_ROBOTS_TAG} \ + \${NGINX_LARGE_CLIENT_HEADER_BUFFERS_SIZE} \ + \${NGINX_PORT} \ + \${NGINX_ROOT} \ + \${NGINX_STATIC_CACHE_CONTROL_HEADER} \ + \${NGINX_STATIC_X_CACHE_CONTROL_TTL_HEADER} \ + \${NGINX_STATIC_X_ROBOTS_TAG} \ + \${NGINX_X_CONTENT_TYPE_OPTIONS} \ + \${NGINX_X_XSS_PROTECTION} \ + " <${NGINX_DEFAULT_CONF_TEMPLATE} >${NGINX_DEFAULT_CONF} +fi +exec "$@" diff --git a/doc/Node-Changelog/1.0.0.md b/doc/Node-Changelog/1.0.0.md new file mode 100644 index 0000000..cc2cf2b --- /dev/null +++ b/doc/Node-Changelog/1.0.0.md @@ -0,0 +1,5 @@ +2024-04-22 +=== + +### Added +- Create node docker images based on node 20 LTS version diff --git a/docker.yml.job.template b/docker.yml.job.template new file mode 100644 index 0000000..95cd11e --- /dev/null +++ b/docker.yml.job.template @@ -0,0 +1,26 @@ + ${GITHUB_JOB_ID}: + name: Build and push ${NODE_VERSION_TAG} ${VARIANT} image + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: ./${BUILD_DIR} + file: ./${BUILD_DIR}/Dockerfile + cache-from: type=gha,scope=${VARIANT_TAG} + cache-to: type=gha,scope=${VARIANT_TAG},mode=max + push: true + tags: anzusystems/node:${{ github.ref_name }}-${VARIANT_TAG},anzusystems/node:latest-${VARIANT_TAG} + diff --git a/docker.yml.template b/docker.yml.template new file mode 100644 index 0000000..f151096 --- /dev/null +++ b/docker.yml.template @@ -0,0 +1,15 @@ +# Docker +# Build and push php images to Docker Hub +name: Build and push + +on: + push: + paths: + - 'build/**' + - '.github/**' + branches: + - 'main' + tags: + - '*' + +jobs: diff --git a/template.Dockerfile b/template.Dockerfile new file mode 100644 index 0000000..f3ae849 --- /dev/null +++ b/template.Dockerfile @@ -0,0 +1,100 @@ +FROM node:${NODE_VERSION} + +LABEL maintainer="Lubomir Stanko " + +# ---------------------------------------------------------------------------------------------------------------------- +# ENVIRONMENT VARIABLES +# ---------------------------------------------------------------------------------------------------------------------- +# Common environment variables +ENV CONFIG_OWNER_NAME=node \ + CONFIG_GROUP_NAME=node \ + CONTAINER_STOP_LOG_FILE="/var/www/html/var/log/container_stop.log" \ + COREPACK_HOME="/usr/lib/node/corepack" \ + MAIN_TERMINATED_FILE="/var/www/html/var/log/main-terminated" \ + NPM_CONFIG_LOGLEVEL=notice \ + YARN_CACHE_FOLDER="/var/cache/yarn" \ + YARN_ENABLE_TELEMETRY=0 \ + # Unset yarn version - it could break CI and we don't need it + YARN_VERSION= +# Packages +ENV RUN_DEPS="ca-certificates \ + curl \ + g++ \ + gcc \ + gettext-base \ + git \ + gnupg \ + less \ + logrotate \ + lsb-release \ + make \ + openssh-client \ + procps \ + vim \ + wget" + +# ---------------------------------------------------------------------------------------------------------------------- +# PACKAGES +# ---------------------------------------------------------------------------------------------------------------------- +RUN apt-get update && \ + apt-get install -y \ + ${RUN_DEPS} \ + supervisor=${SUPERVISOR_VERSION}-${SUPERVISOR_PKG_RELEASE} && \ +# Cleanup + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# ---------------------------------------------------------------------------------------------------------------------- +# NPM +# Install static npm version +# ---------------------------------------------------------------------------------------------------------------------- +RUN npm install --location=global npm@${NPM_VERSION} && \ + npm install --location=global auditjs@${AUDITJS_VERSION} && \ + mkdir -p ${COREPACK_HOME} && \ + corepack prepare yarn@${YARN_VERSION} --activate && \ + corepack enable && \ +# Node cache cleanup + npm cache clean --force && \ + yarn cache clean --all +# Versions of local tools +RUN echo "node version: $(node -v) \n" \ + "npm version: $(npm -v) \n" \ + "yarn version: $(yarn -v)" + +# ---------------------------------------------------------------------------------------------------------------------- +# USER SETUP +# ---------------------------------------------------------------------------------------------------------------------- +RUN sed -i 's/^#alias l/alias l/g' /home/node/.bashrc && \ + echo "update-notifier=false" > /home/node/.npmrc && \ + mkdir -p \ + ${YARN_CACHE_FOLDER} \ + /usr/local/lib/node_modules \ + /var/run/supervisor \ + /var/www/html/var && \ + chown node:node -R \ + ${COREPACK_HOME} \ + ${YARN_CACHE_FOLDER} \ + /home/node/.npmrc \ + /usr/local/bin \ + /usr/local/lib/node_modules \ + /var/run/supervisor \ + /var/www/html + +#### +#### + +# ---------------------------------------------------------------------------------------------------------------------- +# RUN CONFIGURATION +# ---------------------------------------------------------------------------------------------------------------------- +COPY --chown=${CONFIG_OWNER_NAME}:${CONFIG_GROUP_NAME} ./etc /etc +COPY --chown=${CONFIG_OWNER_NAME}:${CONFIG_GROUP_NAME} ./usr /usr + +# ---------------------------------------------------------------------------------------------------------------------- +# RUN +# Run setup and entrypoint start +# ---------------------------------------------------------------------------------------------------------------------- +WORKDIR /var/www/html + +USER node + +ENTRYPOINT ["docker-custom-entrypoint"] diff --git a/update.sh b/update.sh new file mode 100755 index 0000000..d3a4e58 --- /dev/null +++ b/update.sh @@ -0,0 +1,133 @@ +#!/bin/bash +set -Eeuo pipefail + +# Source all versions +# shellcheck disable=SC1091 +. ./versions.conf + +DEFAULT_IFS=${IFS} +TEMPLATE_DOCKERFILE=template.Dockerfile +TMP_DOCKERFILE_FILE="/tmp/node_tmp_variant_Dockerfile" +TMP_FINAL_DOCKERFILE_FILE="/tmp/node_tmp_final_variant_Dockerfile" + +declare -A VERSION_LIST +VERSION_LIST=( + [20]="${NODE20_VERSION}" +) + +VARIANTS_LIST='base nginx nginx-browsers' + +# Get all parameter names to replace in Dockerfile +REPLACE_PARAMETERS=$(sed .github/workflows/docker.yml + +readarray -t VERSION_TAGS < <(printf '%s\n' "${!VERSION_LIST[@]}" | sort -V) +for version in "${VERSION_TAGS[@]}"; do + NODE_VERSION_TAG="node${version//./}" + NODE_VERSION=${VERSION_LIST[$version]} + for VARIANT in ${VARIANTS_LIST}; do + # Cleanup before script run + rm -f ${TMP_DOCKERFILE_FILE} + # Temporary variant dockerfile + IFS='-' + for type in ${VARIANT}; do + variant_dockerfile_name="variant-${type}.Dockerfile" + if [ -f "$variant_dockerfile_name" ]; then + cat "$variant_dockerfile_name" >>${TMP_DOCKERFILE_FILE} + fi + done + IFS=${DEFAULT_IFS} + # Variables + export NODE_VERSION_TAG + export NODE_VERSION + export VARIANT + export BUILD_DIR="build/${NODE_VERSION_TAG}/${VARIANT}" + TMP_VARIANT_TAG="${NODE_VERSION_TAG}-${VARIANT}" + export VARIANT_TAG="${TMP_VARIANT_TAG//-base/}" + TMP_GITHUB_JOB_ID="${NODE_VERSION_TAG}-${VARIANT}" + export GITHUB_JOB_ID="${TMP_GITHUB_JOB_ID//_base/}" + echo "Creating variant folder ${BUILD_DIR}" + mkdir -p "${BUILD_DIR}" + + GENERATED_DOCKERFILE="${BUILD_DIR}/Dockerfile" + echo "Generating dockerfile ${GENERATED_DOCKERFILE}" + { + generated_warning + cat "${TEMPLATE_DOCKERFILE}" + } >"${TMP_FINAL_DOCKERFILE_FILE}" + + gawk -i inplace -v dockerfile="${TMP_DOCKERFILE_FILE}" ' + $1 == "####" { ia = 0 } + !ia { print } + $1 == "####" { ia = 1; ac = 0; if (system("test -f " dockerfile) != 0) { ia = 0 } } + ia { ac++ } + ia && ac == 1 { system("cat " dockerfile) } + ' "${TMP_FINAL_DOCKERFILE_FILE}" + + envsubst "${REPLACE_PARAMETERS}" <"${TMP_FINAL_DOCKERFILE_FILE}" >"${GENERATED_DOCKERFILE}" + + echo "Copying common config files to ${BUILD_DIR}" + if [ -d "config/all" ]; then + cp -ar config/all/. "${BUILD_DIR}" + fi + IFS='-' + for type in ${VARIANT}; do + if [ -d "config/all-${type}" ]; then + echo "Copying common ${type} config files to ${BUILD_DIR}" + cp -ar "config/all-${type}/." "${BUILD_DIR}" + fi + done + + echo "Copying common ${NODE_VERSION_TAG} config files to ${BUILD_DIR}" + if [ -d "config/all-${version}" ]; then + cp -ar "config/all-${version}/." "${BUILD_DIR}" + fi + IFS='-' + for type in ${VARIANT}; do + if [ -d "config/all-${version}-${type}" ]; then + echo "Copying common ${NODE_VERSION_TAG} ${type} config files to ${BUILD_DIR}" + cp -ar "config/all-${version}-${type}/." "${BUILD_DIR}" + fi + done + + for type in ${VARIANT}; do + if [ -d "config/${type}" ]; then + echo "Copying ${type} config files to ${BUILD_DIR}" + cp -ar "config/${type}/." "${BUILD_DIR}" + fi + done + + for type in ${VARIANT}; do + if [ -d "config/${version}-${type}" ]; then + echo "Copying ${type} config files to ${BUILD_DIR}" + cp -ar "config/${version}-${type}/." "${BUILD_DIR}" + fi + done + IFS=${DEFAULT_IFS} + + envsubst "\ + \${GITHUB_JOB_ID} \ + \${BUILD_DIR} \ + \${NODE_VERSION_TAG} \ + \${VARIANT} \ + \${VARIANT_TAG} \ + " >.github/workflows/docker.yml + # Cleanup after script run + rm -f ${TMP_DOCKERFILE_FILE} + done +done diff --git a/variant-browsers.Dockerfile b/variant-browsers.Dockerfile new file mode 100644 index 0000000..99f4e3f --- /dev/null +++ b/variant-browsers.Dockerfile @@ -0,0 +1,55 @@ +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# BROWSERS SETUP START +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ---------------------------------------------------------------------------------------------------------------------- +ENV DBUS_SESSION_BUS_ADDRESS=/dev/null +# Install Needed packages +RUN apt-get update && \ + apt-get install --no-install-recommends -y \ + bzip2 \ + dbus-x11 \ + fonts-liberation \ + libasound2 \ + libgbm-dev \ + libgbm1 \ + libgconf-2-4 \ + libgtk-3-0 \ + libgtk2.0-0 \ + libnotify-dev \ + libnss3 \ + libu2f-udev \ + libxkbcommon0 \ + libxss1 \ + libxtst6 \ + xauth \ + xdg-utils \ + xvfb && \ +# Cleanup + apt-get clean && \ + rm -r /var/lib/apt/lists/* +# Install Google Chrome +RUN wget -q -O /usr/src/google-chrome-stable_current_amd64.deb "https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_${CHROME_VERSION}-1_amd64.deb" && \ + apt-get update && \ + dpkg -i /usr/src/google-chrome-stable_current_amd64.deb ; \ + apt-get install -f -y && \ + rm -f /usr/src/google-chrome-stable_current_amd64.deb && \ +# Cleanup + apt-get clean && \ + rm -r /var/lib/apt/lists/* +# Install Firefox +RUN wget -q -O /tmp/firefox.tar.bz2 "https://download-installer.cdn.mozilla.net/pub/firefox/releases/${FIREFOX_VERSION}/linux-x86_64/en-US/firefox-${FIREFOX_VERSION}.tar.bz2" && \ + tar -C /opt -xjf /tmp/firefox.tar.bz2 && \ + rm -f /tmp/firefox.tar.bz2 && \ + ln -fs /opt/firefox/firefox /usr/bin/firefox +# Versions of local tools +RUN echo "node version: $(node -v) \n" \ + "npm version: $(npm -v) \n" \ + "yarn version: $(yarn -v) \n" \ + "debian version: $(cat /etc/debian_version) \n" \ + "Chrome version: $(google-chrome --version) \n" \ + "Firefox version: $(firefox --version) \n" \ + "git version: $(git --version) \n" \ + "whoami: $(whoami) \n" +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# BROWSERS SETUP END +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/variant-nginx.Dockerfile b/variant-nginx.Dockerfile new file mode 100644 index 0000000..d55b3d1 --- /dev/null +++ b/variant-nginx.Dockerfile @@ -0,0 +1,66 @@ +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# NGINX SETUP START +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ---------------------------------------------------------------------------------------------------------------------- +# NGINX ENVIRONMENT VARIABLES +ENV NGINX_ACCESS_LOG="/var/log/nginx/access.log main" \ + NGINX_ACCESS_LOG_HEALTHCHECK_ENABLED="0" \ + NGINX_API_LOCATION=/api \ + NGINX_API_V1_LOCATION=/api/v1 \ + NGINX_API_X_ROBOTS_TAG="noindex, nofollow, noarchive, nosnippet" \ + NGINX_CLIENT_MAX_BODY_SIZE=1m \ + NGINX_DOCUMENTS_X_ROBOTS_TAG="noindex, nofollow, noarchive, nosnippet" \ + NGINX_ERROR_LOG="/var/log/nginx/error.log warn" \ + NGINX_HTML_X_ROBOTS_TAG="noindex, nofollow, noarchive, nosnippet" \ + NGINX_KEEPALIVE_REQUESTS=10000 \ + NGINX_KEEPALIVE_TIMEOUT=650 \ + NGINX_LARGE_CLIENT_HEADER_BUFFERS_SIZE=16k \ + NGINX_PORT=8080 \ + NGINX_PROXYPASS_CONFIG=false \ + NGINX_ROOT=/var/www/html/public \ + NGINX_SERVER_TOKENS="off" \ + NGINX_STATIC_CACHE_CONTROL_HEADER="public, max-age=31557600, s-maxage=31557600" \ + NGINX_STATIC_X_CACHE_CONTROL_TTL_HEADER="604800" \ + NGINX_STATIC_X_ROBOTS_TAG="noindex, nofollow, noarchive, nosnippet" \ + NGINX_UPSTREAM_API_PORT=8085 \ + NGINX_UPSTREAM_API_V1_PORT=8285 \ + NGINX_UPSTREAM_WEBSOCKET_PORT=3005 \ + NGINX_WEBSOCKET_LOCATION=/ws \ + NGINX_WEBSOCKET_X_ROBOTS_TAG="noindex, nofollow, noarchive, nosnippet" \ + NGINX_WORKER_CONNECTIONS=1024 \ + NGINX_WORKER_PROCESSES=1 \ + NGINX_WORKER_RLIMIT_NOFILE=65535 \ + NGINX_X_CONTENT_TYPE_OPTIONS="nosniff" \ + NGINX_X_XSS_PROTECTION="1; mode=block" +# ---------------------------------------------------------------------------------------------------------------------- +# NGINX +RUN DEBIAN_FRONTEND=noninteractive && \ + NGINX_KEYRING=/usr/share/keyrings/nginx-archive-keyring.gpg && \ + NGINX_REPO="$(lsb_release -c -s) nginx" && \ + curl -fsSL https://nginx.org/keys/nginx_signing.key | gpg --dearmor -o ${NGINX_KEYRING} && \ + echo "deb [signed-by=${NGINX_KEYRING}] http://nginx.org/packages/debian ${NGINX_REPO}" > /etc/apt/sources.list.d/nginx.list && \ + apt-get update && \ + apt-get install --no-install-recommends --no-install-suggests -y \ + nginx=${NGINX_VERSION}-${NGINX_PKG_RELEASE} \ + nginx-module-xslt=${NGINX_VERSION}-${NGINX_PKG_RELEASE} \ + nginx-module-geoip=${NGINX_VERSION}-${NGINX_PKG_RELEASE} \ + nginx-module-image-filter=${NGINX_VERSION}-${NGINX_PKG_RELEASE} \ + nginx-module-njs=${NGINX_VERSION}+${NGINX_NJS_VERSION}-${NGINX_PKG_RELEASE} && \ +# Cleanup + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# ---------------------------------------------------------------------------------------------------------------------- +# NGINX LOGGING AND USER SETUP +# Create PID folders and forward nginx logs to docker log collector +RUN ln -sf /dev/stdout /var/log/nginx/access.log && \ + ln -sf /dev/stderr /var/log/nginx/error.log && \ + mkdir -p \ + /run/nginx && \ + chown node:node -R \ + /etc/nginx \ + /run/nginx \ + /var/log/nginx +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# NGINX SETUP END +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/versions.conf b/versions.conf new file mode 100644 index 0000000..038e8b5 --- /dev/null +++ b/versions.conf @@ -0,0 +1,17 @@ +export NODE20_VERSION=20.12.2 +# Nginx version +export NGINX_VERSION=1.24.0 +export NGINX_NJS_VERSION=0.8.3 +export NGINX_PKG_RELEASE=1~bookworm +# Supervisor version +export SUPERVISOR_VERSION=4.2.5 +export SUPERVISOR_PKG_RELEASE=1 +# AuditJS version +export AUDITJS_VERSION=4.0.45 +# NPM version +export NPM_VERSION=10.5.2 +# Yarn version +export YARN_VERSION=4.1.1 +# Browsers +export CHROME_VERSION=124.0.6367.60 +export FIREFOX_VERSION=125.0.1