Docker compose setup (#513)

* fix password reset bug

* self hosted mode middleware changes on  pages

* fix lint

* wip: self hosted changes

* wip: self hosted frontend changes

* wip self hosted mode changes

* typo correction

* remove commented logic

* fix env variable names

* fix lint issues

* fix minor updates

* #445 Switched from single monolithic docker image to a docker-compose
 orchestrated network of services

* Automatically configures shared secret

* Working through some issues

* Use local file storage

* Moved the dockerfiles

* Fixed some issues when building from clean

* Corrected workflow

* Hopefully schedules everything correctly now

* Prep storage for worker process as well

* .env files are required

* Pinned dependency versions

* Disable self hosted in the client as well

* Removed double defaulting logic

* Using regexs is more succinct

* Added FRONT_URL environment variable

* Merge 236e4-self-hosted-mode-changes

* Improve inital user setup

* Finalized the new docker-compose setup

* Fix back-end formatting issues

---------

Co-authored-by: Frank <csskfaves@gmail.com>
Co-authored-by: Don Benjamin <don@webhammer.co.uk>
This commit is contained in:
Julien Nahum
2024-08-05 12:06:20 +02:00
committed by GitHub
parent 6b13f95322
commit 3280e38ee1
49 changed files with 6152 additions and 3431 deletions

49
docker/Dockerfile.api Normal file
View File

@@ -0,0 +1,49 @@
FROM php:8.3-fpm
# syntax=docker/dockerfile:1.3-labs
RUN apt-get update && apt-get install -y libzip-dev libpng-dev postgresql-client libpq-dev && apt-get clean
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
ENV COMPOSER_ALLOW_SUPERUSER=1
RUN docker-php-ext-install pdo pgsql pdo_pgsql gd bcmath zip && pecl install redis && docker-php-ext-enable redis
WORKDIR /usr/share/nginx/html/
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/helpers.php
RUN composer install --ignore-platform-req=php
ADD app ./app
ADD bootstrap ./bootstrap
ADD config ./config
ADD database ./database
ADD public public
ADD routes routes
ADD tests tests
ADD resources resources
ADD storage ./storage
RUN chmod 777 -R storage
# Manually run the command we deleted from composer.json earlier
RUN php artisan package:discover --ansi
COPY docker/php-fpm-entrypoint /usr/local/bin/opnform-entrypoint
COPY docker/generate-api-secret.sh /usr/local/bin/
RUN ln -s /secrets/api.env .env
RUN chmod a+x /usr/local/bin/*
ENTRYPOINT [ "/usr/local/bin/opnform-entrypoint" ]
CMD php-fpm

34
docker/Dockerfile.client Normal file
View File

@@ -0,0 +1,34 @@
FROM node:20-alpine AS javascript-builder
WORKDIR /app
# It's best to add as few files as possible before running the build commands
# as they will be re-run everytime one of those files changes.
#
# It's possible to run npm install with only the package.json and package-lock.json file.
ADD ./client/package.json ./client/package-lock.json ./
# Install git and other necessary build tools
RUN apk add --no-cache git
# Clear npm cache, remove existing node_modules, and install dependencies
RUN npm cache clean --force && \
rm -rf node_modules && \
npm install
# Explicitly install the correct version of esbuild
# RUN npm install esbuild@0.21.5
ADD ./client/ /app/
RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY --from=javascript-builder /app/.output/ /app/
RUN ls /app/
RUN ln -s /secrets/client.env .env
ADD ./docker/node-entrypoint /entrypoint.sh
RUN chmod a+x /entrypoint.sh
ENTRYPOINT [ "/entrypoint.sh" ]
CMD [ "node", "./server/index.mjs" ]

View File

@@ -1,20 +1,21 @@
#!/bin/bash -e
main() {
( flock -n 100 || wait_for_other_instance; generate_api_secrets) 100> /var/lock/api_secret.lock
generate_api_secrets
}
generate_api_secrets() {
if ! is_configured; then
echo "Generating shared secret..."
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"
add_secret_to_env_file /secrets/client.env NUXT_API_SECRET "$SECRET"
add_secret_to_env_file /secrets/api.env FRONT_API_SECRET "$SECRET"
fi
}
random_string() {
array=()
for i in {a..z} {A..Z} {0..9};
for i in {a..z} {A..Z} {0..9};
do
array[$RANDOM]=$i
done
@@ -27,21 +28,15 @@ add_secret_to_env_file() {
VAR=$2
VAL=$3
grep "^$VAR=" "$FILE" || ( echo "$VAR=" >> "$FILE" )
grep -q "^$VAR=" "$FILE" 2>/dev/null || ( 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
grep -q "FRONT_API_SECRET=.\+" .env 2>/dev/null
}
main

View File

@@ -14,32 +14,27 @@ server {
index index.html index.htm index.php;
location / {
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;
proxy_http_version 1.1;
proxy_pass http://ui:3000;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
location /api/ {
set $original_uri $uri;
try_files $uri $uri/ /index.php$is_args$args;
}
location /local/temp/ {
set $original_uri $uri;
try_files $uri $uri/ /index.php$is_args$args;
}
location /forms/assets/ {
location ~/(api|open|local\/temp|forms\/assets)/ {
set $original_uri $uri;
try_files $uri $uri/ /index.php$is_args$args;
}
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php-fpm-opnform-site.sock;
fastcgi_pass api:9000;
fastcgi_index index.php;
include fastcgi.conf;
include fastcgi_params;
#fastcgi_param SCRIPT_FILENAME /usr/share/nginx/html/$fastcgi_script_name;
fastcgi_param SCRIPT_FILENAME /usr/share/nginx/html/public/index.php;
fastcgi_param REQUEST_URI $api_uri;
}
}

30
docker/node-entrypoint Normal file
View File

@@ -0,0 +1,30 @@
#!/bin/sh
main() {
if [ "$1" == "bash" ]; then
"$@"
else
wait_for_api_secret
if [ ! -f .env ] && [ -f /secrets/client.env ]; then
ln -sf /secrets/client.env .env
fi
if [ -f .env ]; then
. .env
else
echo "Warning: .env file not found"
fi
run_server "$@"
fi
}
wait_for_api_secret() {
until [ -f /secrets/configured ]; do
echo "Waiting for api secret..."
sleep 1
done
}
run_server() {
echo "Running " node "$@"
"$@"
}
main "$@"

View File

@@ -1,27 +0,0 @@
#!/bin/bash -e
echo + . ~nuxt/.nvm/nvm.sh
. ~nuxt/.nvm/nvm.sh
echo + nvm install --no-progress 20
nvm install --no-progress 20
echo + nvm use 20
nvm use 20
cd /app/nuxt/server/
export NUXT_PRIVATE_API_BASE=http://localhost/api
echo + . /app/client/.env
[ -f /app/client/.env ] && . /app/client/.env || echo "Environment file missing!"
[ "x$NUXT_API_SECRET" != "x" ] || (
echo + generate-api-secret.sh
generate-api-secret.sh
)
echo + eval \$\(sed 's/^/export /' \< /app/client/.env\)
eval $(sed 's/^/export /' < /app/client/.env)
echo + node index.mjs
node index.mjs

115
docker/php-fpm-entrypoint Normal file
View File

@@ -0,0 +1,115 @@
#!/bin/bash
main() {
read_env
prep_file_permissions
prep_storage
if is_master "$@"; then
prep_laravel_secrets
wait_for_db
apply_db_migrations
run_init_project
mark_ready
else
wait_for_ready
wait_for_db
fi
read_env
run_server "$@"
}
is_master() {
echo "$@" | grep -q php-fpm
}
read_env() {
#set +x
[ -f .env ] || touch .env
. .env
#set -x
}
prep_file_permissions() {
chmod a+x ./artisan
}
prep_laravel_secrets() {
read_env
[ "x$APP_KEY" != "x" ] || {
echo "Generating Laravel key..."
grep -q "APP_KEY=" .env || {
echo "APP_KEY=" >> .env
}
./artisan key:generate
read_env
}
[ "x$JWT_SECRET" != "x" ] || {
echo "Generating Laravel Secret..."
./artisan jwt:secret -f
read_env
}
[ "x$FRONT_API_SECRET" != "x" ] || {
echo "Generating Shared Client Secret..."
/usr/local/bin/generate-api-secret.sh
read_env
}
echo "Done with secrets"
}
apply_db_migrations() {
echo "Running DB Migrations"
./artisan migrate
}
run_init_project() {
echo "Running app:init-project command"
./artisan app:init-project
}
wait_for_ready() {
echo "Checking keys have been generated"
until [ -f /secrets/configured ]; do
sleep 1;
echo "Waiting for keys to generate"
done
}
mark_ready() {
touch /secrets/configured
}
wait_for_db() {
until ./artisan migrate:status 2>&1 | grep -q -E "(Migration table not found|Migration name)"; do
echo "Waiting for DB to bootup"
sleep 1
done
}
run_server() {
echo "Booting $@"
read_env
/usr/local/bin/docker-php-entrypoint "$@"
}
prep_storage() {
[ -L storage ] || {
echo "Backing up initial storage directory"
rm -rf /etc/initial-storage
mv ./storage /etc/initial-storage
}
[ -d /persist/storage ] || {
echo "Initialising blank storage dir"
mkdir -p /persist
cp -a /etc/initial-storage /persist/storage
chmod 777 -R /persist/storage
}
touch /var/log/opnform.log
chown www-data /var/log/opnform.log
echo "Linking persistent storage into app"
ln -t . -sf /persist/storage
}
main "$@"

View File

@@ -1,45 +0,0 @@
#!/bin/bash -ex
[ -L /app/storage ] || {
echo "Backing up initial storage directory"
rm -rf /etc/initial-storage
mv /app/storage /etc/initial-storage
}
[ -d /persist/storage ] || {
echo "Initialising blank storage dir"
mkdir -p /persist
cp -a /etc/initial-storage /persist/storage
chmod 777 -R /persist/storage
}
touch /var/log/opnform.log
chown opnform /var/log/opnform.log
echo "Linking persistent storage into app"
ln -t /app -sf /persist/storage
read_env() {
set +x
. /app/.env
set -x
}
read_env
[ "x$APP_KEY" != "x" ] || {
artisan key:generate
read_env
}
[ "x$JWT_SECRET" != "x" ] || {
artisan jwt:secret -f
read_env
}
[ "x$FRONT_API_SECRET" != "x" ] || {
generate-api-secret.sh
read_env
}
/usr/sbin/php-fpm8.1
tail -f /var/log/opnform.log

View File

@@ -1,18 +0,0 @@
[opnform]
user = opnform
group = opnform
listen = /var/run/php-fpm-opnform-site.sock
listen.owner = www-data
listen.group = www-data
php_admin_value[disable_functions] = exec,passthru,shell_exec,system
php_admin_flag[allow_url_fopen] = off
php_admin_value[error_log] = /var/log/opnform.log
; Choose how the process manager will control the number of child processes.
pm = dynamic
pm.max_children = 75
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.process_idle_timeout = 10s
clear_env = no

View File

@@ -1,48 +0,0 @@
#!/bin/bash -ex
DATA_DIR=/persist/pgsql/data
CONFIG_FILE=/etc/postgresql/postgresql.conf
PG_BASE=/usr/lib/postgresql/15/
touch $CONFIG_FILE
mkdir -p $DATA_DIR
chown postgres -R $DATA_DIR
chmod 0700 $DATA_DIR
set +x
. /app/.env
set -x
test -f $DATA_DIR/postgresql.conf || NEW_DB=true
if [ "x$NEW_DB" != "x" ]; then
echo "No database files found. Initialising blank database"
sudo -u postgres $PG_BASE/bin/initdb -D $DATA_DIR
fi
sudo -u postgres $PG_BASE/bin/postgres -D $DATA_DIR -c config_file=$CONFIG_FILE &
wait_for_database_to_be_ready() {
while ! (echo "select version()" | psql -U $DB_USERNAME); do
echo "Waiting 5 seconds for the database to come up"
sleep 5;
done
}
if [ "x$NEW_DB" != "x" ]; then
echo "Creating database users"
wait_for_database_to_be_ready
psql -U postgres <<EOF
CREATE ROLE $DB_USERNAME LOGIN PASSWORD '$DB_PASSWORD';
CREATE DATABASE $DB_DATABASE;
\c $DB_DATABASE;
GRANT ALL ON DATABASE $DB_DATABASE TO $DB_USERNAME;
GRANT ALL ON SCHEMA public TO $DB_USERNAME;
EOF
fi
wait_for_database_to_be_ready
sudo -u opnform artisan migrate --force
wait

View File

@@ -1,7 +0,0 @@
#!/bin/bash -ex
sysctl vm.overcommit_memory=1
mkdir -p /persist/redis/data
chown redis -R /persist/redis/data
sudo -u redis /usr/bin/redis-server /etc/redis/redis.conf

View File

@@ -1,44 +0,0 @@
[supervisord]
nodaemon=true
logfile=/dev/null
logfile_maxbytes=0
user=root
[program:nginx]
command=/usr/sbin/nginx
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
redirect_stderr=true
[program:php-fpm]
command=/usr/local/bin/php-fpm-wrapper.sh
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
redirect_stderr=true
[program:php-queue]
process_name=%(program_name)s_%(process_num)02d
command=/usr/local/bin/artisan queue:work
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
redirect_stderr=true
numprocs=5
[program:postgres]
command=/usr/local/bin/postgres-wrapper.sh
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
redirect_stderr=true
[program:redis]
command=/usr/local/bin/redis-wrapper.sh
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
redirect_stderr=true
[program:nuxt-backend]
command=/usr/local/bin/nuxt-wrapper.sh
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
redirect_stderr=true
user=nuxt