diff --git a/.dockerignore b/.dockerignore index 331d387..d339a28 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1,3 @@ -.git -Dockerfile +/.git +/Dockerfile +/data diff --git a/.env.docker b/.env.docker index e1fc74b..123f0fa 100644 --- a/.env.docker +++ b/.env.docker @@ -28,14 +28,14 @@ REDIS_HOST=127.0.0.1 REDIS_PASSWORD=null REDIS_PORT=6379 -MAIL_MAILER=smtp -MAIL_HOST=smtp.mailtrap.io -MAIL_PORT=2525 -MAIL_USERNAME=null -MAIL_PASSWORD=null -MAIL_ENCRYPTION=null -MAIL_FROM_ADDRESS=null -MAIL_FROM_NAME="${APP_NAME}" +MAIL_MAILER=log +MAIL_HOST= +MAIL_PORT= +MAIL_USERNAME= +MAIL_PASSWORD= +MAIL_ENCRYPTION= +MAIL_FROM_ADDRESS= +MAIL_FROM_NAME= AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= diff --git a/Dockerfile b/Dockerfile index e136a9d..60856bd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ ARG PHP_PACKAGES="php8.1 composer php8.1-common php8.1-pgsql php8.1-redis php8.1 php8.1-simplexml php8.1-bcmath php8.1-gd php8.1-curl php8.1-zip\ php8.1-imagick php8.1-bz2 php8.1-gmp php8.1-int php8.1-pcov php8.1-soap php8.1-xsl" -FROM node:16 AS javascript-builder +FROM node:20-alpine AS javascript-builder WORKDIR /app # It's best to add as few files as possible before running the build commands @@ -10,15 +10,12 @@ WORKDIR /app # # It's possible to run npm install with only the package.json and package-lock.json file. -ADD package.json package-lock.json ./ +ADD client/package.json client/package-lock.json ./ RUN npm install -ADD resources /app/resources -ADD public /app/public -ADD tailwind.config.js vite.config.js postcss.config.js /app/ +ADD client /app/ RUN npm run build - # syntax=docker/dockerfile:1.3-labs FROM --platform=linux/amd64 ubuntu:23.04 AS php-dependency-installer @@ -29,10 +26,18 @@ RUN apt-get update \ WORKDIR /app ADD composer.json composer.lock artisan ./ + +# NOTE: The project would build more reliably if all php files were added before running +# composer install. This would though introduce a dependency which would cause every +# dependency to be re-installed each time any php file is edited. It may be necessary in +# future to remove this 'optimisation' by moving the `RUN composer install` line after all +# the following ADD commands. + # Running artisan requires the full php app to be installed so we need to remove the # post-autoload command from the composer file if we want to run composer without # adding a dependency to all the php files. RUN sed 's_@php artisan package:discover_/bin/true_;' -i composer.json +ADD app/helpers.php /app/app/helpers.php RUN composer install --ignore-platform-req=php ADD app /app/app @@ -61,18 +66,22 @@ ARG PHP_PACKAGES RUN apt-get update \ && apt-get install -y \ supervisor nginx sudo postgresql-15 redis\ - $PHP_PACKAGES php8.1-fpm\ + $PHP_PACKAGES php8.1-fpm wget\ && apt-get clean +RUN wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.39.3/install.sh | bash +RUN . /root/.nvm/nvm.sh && nvm install 20 -ADD docker/postgres-wrapper.sh docker/php-fpm-wrapper.sh docker/redis-wrapper.sh /usr/local/bin/ +ADD docker/postgres-wrapper.sh docker/php-fpm-wrapper.sh docker/redis-wrapper.sh docker/nuxt-wrapper.sh docker/generate-api-secret.sh /usr/local/bin/ ADD docker/php-fpm.conf /etc/php/8.1/fpm/pool.d/ ADD docker/nginx.conf /etc/nginx/sites-enabled/default ADD docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf -ADD .env.docker .env ADD . . +ADD .env.docker .env +ADD client/.env.docker client/.env -COPY --from=javascript-builder /app/public/build/ ./public/build/ +COPY --from=javascript-builder /app/.output/ ./nuxt/ +RUN cp -r nuxt/public . COPY --from=php-dependency-installer /app/vendor/ ./vendor/ RUN chmod a+x /usr/local/bin/*.sh /app/artisan \ diff --git a/README.md b/README.md index 087db45..90d0dfc 100644 --- a/README.md +++ b/README.md @@ -84,12 +84,18 @@ The `-v` argument creates a local directory called `my-opnform-data` which will The `--name` argument names the running container so that you can refer back to it later, with e.g. `docker stop opnform`. You can use any name you'd like. -#### Using a custom .env file +#### Using custom .env files -If you have a custom env file you can use this like so: +If you have custom env file you can use them like so: +Custom Laravel .env file: ``` -docker run --name opnform -v $PWD/my-custom-env-file.env:/app/.env -v $PWD/my-opnform-data:/persist -p 80:80 jhumanj/opnform +docker run --name opnform -v $PWD/custom-laravel-env-file.env:/app/.env -v $PWD/my-opnform-data:/persist -p 80:80 jhumanj/opnform +``` + +Custom Nuxt .env file: +``` +docker run --name opnform -v $PWD/custom-nuxt-env-file.env:/app/client/.env -v $PWD/my-opnform-data:/persist -p 80:80 jhumanj/opnform ``` This would load load in the env file located at `my-custom-env-file.env`, note that if you are creating a .env file for use like this it's best to start from the `.docker.env` example file as there are slightly different defaults for the dockerized setup. diff --git a/client/.env.docker b/client/.env.docker new file mode 100644 index 0000000..56ca9f2 --- /dev/null +++ b/client/.env.docker @@ -0,0 +1,13 @@ +NUXT_LOG_LEVEL= +NUXT_PUBLIC_APP_URL=http://localhost/ +NUXT_PUBLIC_API_BASE=http://localhost/api +NUXT_PUBLIC_AI_ENABLED= +NUXT_PUBLIC_AMPLITUDE_CODE= +NUXT_PUBLIC_CRISP_WEBSITE_ID= +NUXT_PUBLIC_CUSTOM_DOMAINS_ENABLED= +NUXT_PUBLIC_ENV=local +NUXT_PUBLIC_GOOGLE_ANALYTICS_CODE= +NUXT_PUBLIC_H_CAPTCHA_SITE_KEY= +NUXT_PUBLIC_PAID_PLANS_ENABLED= +NUXT_PUBLIC_S3_ENABLED= +NUXT_API_SECRET= diff --git a/client/.gitignore b/client/.gitignore index 7f06606..f2b64c1 100644 --- a/client/.gitignore +++ b/client/.gitignore @@ -23,3 +23,4 @@ logs .env .env.* !.env.example +!.env.docker diff --git a/docker/generate-api-secret.sh b/docker/generate-api-secret.sh new file mode 100644 index 0000000..6f55494 --- /dev/null +++ b/docker/generate-api-secret.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +main() { + ( flock -n 100 || wait_for_other_instance; generate_api_secrets) 100> /var/lock/api_secret.lock +} + +generate_api_secrets() { + if ! is_configured; then + SECRET=$(random_string) + add_secret_to_env_file /app/client/.env NUXT_API_SECRET $SECRET + add_secret_to_env_file /app/.env FRONT_API_SECRET $SECRET + fi +} + +random_string() { + array=() + for i in {a..z} {A..Z} {0..9}; + do + array[$RANDOM]=$i + done + printf %s ${array[@]::8} $'\n' +} + +add_secret_to_env_file() { + FILE=$1 + TEMP_FILE=/tmp/env.$$ + VAR=$2 + VAL=$3 + + grep "^$VAR=" $FILE || ( echo $VAR= >> $FILE ) + + cp $FILE $TEMP_FILE + sed "s/^$VAR=.*$/$VAR=$VAL/" -i $TEMP_FILE + cat $TEMP_FILE > $FILE +} + +wait_for_other_instance() { + while ! is_configured; do + sleep 1; + done +} + +is_configured() { + grep -q "FRONT_API_SECRET=.\+" /app/.env +} + +main diff --git a/docker/nginx.conf b/docker/nginx.conf index b8fc41b..ad6000d 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -1,3 +1,8 @@ +map $original_uri $api_uri { + ~^/api(/.*$) $1; + default $original_uri; +} + server { listen 80; server_name opnform; @@ -9,7 +14,15 @@ server { index index.html index.htm index.php; location / { - try_files $uri $uri/ /index.php$is_args$args; + proxy_pass http://localhost:3000; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + } + + location /api/ { + set $original_uri $uri; + try_files $uri $uri/ /index.php$is_args$args; } location ~ \.php$ { @@ -17,6 +30,7 @@ server { fastcgi_pass unix:/var/run/php-fpm-opnform-site.sock; fastcgi_index index.php; include fastcgi.conf; - } + fastcgi_param REQUEST_URI $api_uri; + } } diff --git a/docker/nuxt-wrapper.sh b/docker/nuxt-wrapper.sh new file mode 100644 index 0000000..9ed0b1d --- /dev/null +++ b/docker/nuxt-wrapper.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +. /root/.nvm/nvm.sh +nvm install 20 +nvm use 20 + +cd /app/nuxt/server/ + +. /app/client/.env +[ "x$NUXT_API_SECRET" != "x" ] || generate-api-secret.sh + +sed 's/^/export /' < /app/.nuxt.env > env.sh + +. env.sh + +node index.mjs diff --git a/docker/php-fpm-wrapper.sh b/docker/php-fpm-wrapper.sh index 8cf5e0e..4387f14 100644 --- a/docker/php-fpm-wrapper.sh +++ b/docker/php-fpm-wrapper.sh @@ -30,6 +30,11 @@ ln -sf /persist/storage /app/storage . /app/.env } +[ "x$FRONT_API_SECRET" != "x" ] || { + generate-api-secret.sh + . /app/.env +} + /usr/sbin/php-fpm8.1 tail -f /var/log/opnform.log diff --git a/docker/supervisord.conf b/docker/supervisord.conf index d99b06b..bc7a454 100644 --- a/docker/supervisord.conf +++ b/docker/supervisord.conf @@ -40,3 +40,10 @@ stderr_logfile=/dev/stderr stdout_logfile_maxbytes=0 redirect_stderr=true +[program:nuxt-backend] +command=/usr/local/bin/nuxt-wrapper.sh +stdout_logfile=/dev/stdout +stderr_logfile=/dev/stderr +stdout_logfile_maxbytes=0 +redirect_stderr=true +