Files
Cyril Rohr d482f1f708 Fix docker bloat (#21948)
* Refactor Docker build/runtime stages for slimmer images

Split runtime and build dependencies into separate stages and build the app in a dedicated stage before runtime copy.

Add a slim prune stage that removes non-runtime source trees, source maps, duplicate enterprise source videos, module test/doc folders, and extra vendored gem artifacts.

This ensures bytes are removed before the final slim copy, so layer size actually decreases while keeping runtime behavior intact.

* Add target-specific Docker image validation in CI

Introduce script/ci/docker_validate_image.sh with validations for slim, slim-bim, and all-in-one images.

Checks include runtime binary presence/absence, plugin asset/module integrity, slim pruning expectations, BIM tooling, and all-in-one API startup/embedded services.

Update docker workflow to run the validator for every matrix target before push.

* fix

* Generate YAML-safe auto Hocuspocus secret

All-in-one startup auto-generates OPENPROJECT_COLLABORATIVE__EDITING__HOCUSPOCUS__SECRET in the entrypoint.

Environment overrides are parsed through YAML, so leading punctuation in the previous charset (e.g. %) could trigger Psych parsing errors and abort boot.

Restrict generated secret characters to alphanumeric to keep parsing stable while preserving high entropy.

* Fix all-in-one hocuspocus runtime and validation

* Fix all-in-one memcached startup handover
2026-02-11 10:40:54 +01:00

163 lines
4.8 KiB
Bash
Executable File

#!/bin/bash
set -e
set -o pipefail
indent() {
sed '/^-----> /! s/^/ /'
}
echo "-----> Starting the all-in-one OpenProject setup at $BASH_SOURCE..."
if [ "$PGDATA" == "" ]; then
echo "No PGDATA environment variable defined. Aborting." | indent
exit 2
fi
PG_STARTUP_WAIT_TIME=${PG_STARTUP_WAIT_TIME:=10}
SUPERVISORD_LOG_LEVEL=${SUPERVISORD_LOG_LEVEL:=info}
dbhost=$(ruby -ruri -e 'puts URI(ENV.fetch("DATABASE_URL")).host')
PLUGIN_GEMFILE_TMP=$(mktemp)
PLUGIN_GEMFILE=$APP_PATH/Gemfile.local
if [ "$PLUGIN_GEMFILE_URL" != "" ]; then
echo "Fetching custom gemfile from ${PLUGIN_GEMFILE_URL}..."
curl -L -o "$PLUGIN_GEMFILE_TMP" "$PLUGIN_GEMFILE_URL"
# set custom plugin gemfile if file is readable and non-empty
if [ -s "$PLUGIN_GEMFILE_TMP" ]; then
mv "$PLUGIN_GEMFILE_TMP" "$PLUGIN_GEMFILE"
chown $APP_USER:$APP_USER "$PLUGIN_GEMFILE"
fi
fi
install_plugins() {
pushd $APP_PATH >/dev/null
if [ -s "$PLUGIN_GEMFILE" ]; then
echo "Installing plugins..."
bundle install
echo "Installing frontend dependencies..."
pushd $APP_PATH/frontend >/dev/null
if [ "$(id -u)" = '0' ]; then
su - $APP_USER -c "cd $APP_PATH/frontend && npm install"
else
npm install
fi
popd >/dev/null
echo "Precompiling new assets..."
bundle exec rake assets:precompile
echo "Plugins installed"
fi
popd >/dev/null
}
stop_memcached_daemon() {
/etc/init.d/memcached stop >/dev/null 2>&1 || true
if command -v pkill >/dev/null 2>&1; then
pkill -x memcached >/dev/null 2>&1 || true
fi
rm -f /var/run/memcached/memcached.pid
}
start_memcached_daemon() {
stop_memcached_daemon
/etc/init.d/memcached start
}
migrate() {
wait_for_postgres
pushd $APP_PATH >/dev/null
start_memcached_daemon
echo "-----> Running migrations..."
bundle exec rake db:migrate
# run seed as app user so created attachments (and folder) belong to app, not root
echo "-----> Seeding database..."
su app -c 'bundle exec rake db:seed'
stop_memcached_daemon
popd >/dev/null
}
check_postgres_connection() {
su postgres -c "$PGBIN/psql $DATABASE_URL -c 'select 1;'"
}
wait_for_postgres() {
retries=${PG_STARTUP_WAIT_TIME}
echo "Trying to contact PostgreSQL server instance or waiting for it to come online."
while ! check_postgres_connection &> /dev/null ; do
if [ $retries -eq 0 ]; then
echo "Unable to contact postgres server:"
check_postgres_connection
else
echo "Waiting for postgres server, $((retries--)) remaining attempts..."
sleep 3
fi
done
}
configure_local_postgres() {
mkdir -p "$PGDATA"
chown -R postgres:postgres "$PGDATA"
# remove any dangling pid
rm -f $PGDATA/postmaster.pid
if [ ! -d /etc/postgresql/$PGVERSION ]; then
echo "No cluster configuration found for postgres $PGVERSION. Creating configuration."
pg_createcluster $PGVERSION main
# remove config we will set further down
sed -i '/^port =/d' /etc/postgresql/$PGVERSION/main/postgresql.conf
sed -i '/^data_directory =/d' /etc/postgresql/$PGVERSION/main/postgresql.conf
# remove unused data directory
rm -rf /var/lib/postgresql/$PGVERSION/main
fi
# Set up pg defaults
echo "host all all 0.0.0.0/0 md5" >> /etc/postgresql/$PGVERSION/main/pg_hba.conf
echo "listen_addresses='0.0.0.0'" >> "$PGCONF_FILE"
echo "port=5432" >> "$PGCONF_FILE"
echo "data_directory='$PGDATA'" >> "$PGCONF_FILE"
}
if [ -z "${DATABASE_URL}" ] || [ "$dbhost" = "127.0.0.1" ]; then
configure_local_postgres
# initialize cluster if it does not exist yet
if [ -f "$PGDATA/PG_VERSION" ]; then
echo "-----> Database cluster already exists, not modifying."
su - postgres -c "$PGBIN/pg_ctl -l /tmp/log -w -o '-c config_file=$PGCONF_FILE' -D '$PGDATA' start" | indent
(install_plugins && migrate) 2>&1 | indent
su - postgres -c "$PGBIN/pg_ctl -D '$PGDATA' stop" | indent
else
echo "-----> Database cluster not found. Creating a new one in $PGDATA..."
PGBIN=$PGBIN PGDATA=$PGDATA ./docker/prod/postgres-db-init | indent
su postgres -c "$PGBIN/pg_ctl -w -l /dev/null -o '-c config_file=$PGCONF_FILE' -D '$PGDATA' start" | indent
su postgres -c "$PGBIN/psql --command \"CREATE USER openproject WITH SUPERUSER PASSWORD 'openproject';\"" | indent
su postgres -c "$PGBIN/createdb -O openproject openproject" | indent
(install_plugins && migrate) 2>&1 | indent
su - postgres -c "$PGBIN/pg_ctl -D '$PGDATA' stop" | indent
fi
else
echo "-----> You're using an external database. Not initializing a local database cluster."
migrate 2>&1 | indent
fi
echo "-----> Database setup finished."
echo " On first installation, the default admin credentials are login: admin, password: admin"
# Ensure supervisord can manage memcached itself without a stale daemon keeping port 11211 busy.
stop_memcached_daemon
echo "-----> Launching supervisord..."
erb -r uri $APP_PATH/docker/prod/supervisord.conf.erb > /etc/supervisor/supervisord.conf
exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf -e ${SUPERVISORD_LOG_LEVEL}