Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
FROM openproject/openproject:17-slim

# --- Phase 1: Install system deps needed at build time ---
USER root
RUN apt-get update \
&& apt-get install -y --no-install-recommends git \
&& rm -rf /var/lib/apt/lists/*

# --- Phase 2: Copy custom plugins into the plugins directory ---
COPY --chown=app:app custom-plugin/openproject-livesolutions /app/plugins/openproject-livesolutions
COPY --chown=app:app custom-plugin/openproject-request-portal /app/plugins/openproject-request-portal

# --- Phase 3: Register plugins via Gemfile.plugins ---
RUN printf "gem 'openproject-livesolutions', path: 'plugins/openproject-livesolutions'\ngem 'openproject-request-portal', path: 'plugins/openproject-request-portal'\n" \
> /app/Gemfile.plugins \
&& chown app:app /app/Gemfile.plugins

# --- Phase 4: Bundle install (unlock deployment mode so new gems resolve) ---
USER root
RUN cd /app \
&& sed -i 's/^BUNDLE_DEPLOYMENT:/#BUNDLE_DEPLOYMENT:/' .bundle/config \
&& chown app:app .bundle/config \
&& su app -c "bundle install" \
&& sed -i 's/^#BUNDLE_DEPLOYMENT:/BUNDLE_DEPLOYMENT:/' .bundle/config \
&& chown app:app .bundle/config

# --- Phase 5: Cleanup git (not needed at runtime) ---
USER root
RUN apt-get purge -y --auto-remove git \
&& rm -rf /var/lib/apt/lists/*

# --- Phase 6: Ensure correct ownership of all app files ---
RUN chown -R app:app /app

USER app
14 changes: 8 additions & 6 deletions control/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
FROM debian:12
FROM postgres:17-bookworm

RUN apt-get update -qq && apt-get install wget gnupg2 -y && rm -rf /var/lib/apt/lists/*
RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
RUN echo "deb http://apt.postgresql.org/pub/repos/apt bookworm-pgdg main" > /etc/apt/sources.list.d/pgdg.list
RUN apt-get update -qq && apt-get install postgresql-9.6 postgresql-10 postgresql-13 -y && rm -rf /var/lib/apt/lists/*
RUN localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8
# Install rsync and compression tools useful for upgrade/restore, plus any PG
# client version we might need if we ever have to convert PG 13 -> 17 in-place.
RUN apt-get update -qq \
&& apt-get install -y --no-install-recommends \
rsync wget ca-certificates locales \
&& rm -rf /var/lib/apt/lists/*

# Keep the same locale the upstream Postgres image already sets.
ENV LANG en_US.utf8

ADD . /control
51 changes: 44 additions & 7 deletions control/backup/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,13 +1,50 @@
#!/bin/bash
# OpenProject offline backup entrypoint.
# Creates a point-in-time physical backup of the PostgreSQL data directory and
# OpenProject assets. This is intended to run when the stack is DOWN, via the
# control compose file.
#
# For a live logical SQL dump (more portable across Postgres major versions),
# use scripts/backup-logical.sh while the stack is up.

set -e
set -o pipefail

timestamp=$(date +%s)
mkdir -p /backups
cd /backups
filename="${timestamp}-pgdata.tar.gz"
echo "Backing up PostgreSQL data into backups/${filename}..."
tar czf "${filename}" -C "$PGDATA" .
filename="${timestamp}-opdata.tar.gz"
echo "Backing up OpenProject assets into backups/${filename}..."
tar czf "${filename}" -C "$OPDATA" .
echo "DONE"

# ---------------------------------------------------------------------------
# 1. Physical PostgreSQL data tarball
# ---------------------------------------------------------------------------
pgdata_file="${timestamp}-pgdata.tar.gz"
echo "Creating physical PostgreSQL data backup: backups/${pgdata_file} ..."
tar czf "${pgdata_file}" -C "$PGDATA" .
echo "Physical backup complete: backups/${pgdata_file}"

# ---------------------------------------------------------------------------
# 2. OpenProject assets tarball
# ---------------------------------------------------------------------------
opdata_file="${timestamp}-opdata.tar.gz"
echo "Creating OpenProject assets backup: backups/${opdata_file} ..."
tar czf "${opdata_file}" -C "$OPDATA" .
echo "Assets backup complete: backups/${opdata_file}"

# ---------------------------------------------------------------------------
# 3. Configuration / customization manifest
# ---------------------------------------------------------------------------
manifest_file="${timestamp}-manifest.txt"
echo "Writing backup manifest: backups/${manifest_file} ..."
{
echo "backup_timestamp=${timestamp}"
echo "backup_date=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo "postgres_version=$(cat "$PGDATA/PG_VERSION" 2>/dev/null || echo unknown)"
echo "opdata_size=$(du -sb "$OPDATA" | cut -f1)"
echo "pgdata_size=$(du -sb "$PGDATA" | cut -f1)"
echo "pgdata_file=${pgdata_file}"
echo "opdata_file=${opdata_file}"
echo "custom_plugin_commit=$(git -C /control rev-parse --short HEAD 2>/dev/null || echo n/a)"
} > "${manifest_file}"

echo "DONE: backups/${manifest_file}"
ls -lh /backups/${timestamp}-*
72 changes: 72 additions & 0 deletions control/restore/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/bin/bash
# OpenProject restore entrypoint.
# Restores a previous backup set into the live PGDATA and OPDATA paths.
# Use this only when the stack is DOWN.
#
# Usage:
# docker compose -f docker-compose.yml -f docker-compose.control.yml \
# run --rm -e RESTORE_TIMESTAMP=1782964243 restore
#
# If RESTORE_TIMESTAMP is omitted, the most recent backup set is used.

set -e
set -o pipefail

BACKUP_DIR=/backups

timestamp="${RESTORE_TIMESTAMP:-}"
if [ -z "$timestamp" ]; then
timestamp="$(ls -1 "$BACKUP_DIR" | grep -E '^[0-9]+-manifest\.txt$' | sort -n | tail -1 | cut -d- -f1)"
fi

if [ -z "$timestamp" ]; then
echo "ERROR: no backup manifest found in $BACKUP_DIR" >&2
exit 1
fi

echo "Restoring backup set: ${timestamp}"

# ---------------------------------------------------------------------------
# 1. Restore OpenProject assets
# ---------------------------------------------------------------------------
opdata_file="${BACKUP_DIR}/${timestamp}-opdata.tar.gz"
if [ -f "$opdata_file" ]; then
echo "Restoring assets from ${opdata_file} ..."
mkdir -p "$OPDATA"
rm -rf "${OPDATA:?}"/*
tar xzf "$opdata_file" -C "$OPDATA"
echo "Assets restored."
else
echo "WARNING: ${opdata_file} not found; skipping asset restore." >&2
fi

# ---------------------------------------------------------------------------
# 2. Restore PostgreSQL physical data
# ---------------------------------------------------------------------------
pgdata_file="${BACKUP_DIR}/${timestamp}-pgdata.tar.gz"
if [ -f "$pgdata_file" ]; then
echo "Restoring PostgreSQL data from ${pgdata_file} ..."
rm -rf "${PGDATA:?}"/*
tar xzf "$pgdata_file" -C "$PGDATA"
chown -R postgres:postgres "$PGDATA"
echo "PostgreSQL data restored."
else
echo "WARNING: ${pgdata_file} not found; skipping PG physical restore." >&2
fi

# ---------------------------------------------------------------------------
# 3. Re-apply container network ACLs (in case an old cluster lacked them)
# ---------------------------------------------------------------------------
if [ -d "$PGDATA" ] && [ -f "$PGDATA/postgresql.conf" ]; then
if ! grep -q "listen_addresses = '\*'" "$PGDATA/postgresql.conf"; then
echo "Ensuring listen_addresses='*' is set..."
echo "listen_addresses = '*'" >> "$PGDATA/postgresql.conf"
fi
if ! grep -q "host all all all md5" "$PGDATA/pg_hba.conf"; then
echo "Ensuring host all all all md5 is set..."
echo "host all all all md5" >> "$PGDATA/pg_hba.conf"
fi
fi

echo "DONE: restore complete for timestamp ${timestamp}."
echo "Start the stack with: docker compose up -d --build --pull always"
46 changes: 36 additions & 10 deletions control/upgrade/scripts/00-db-upgrade.sh
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
#!/bin/bash
# PostgreSQL major-version upgrade script for the OpenProject control plane.
# Uses pg_upgrade when the running (old) Postgres major version is lower than
# the target version supplied by the container image.
#
# IMPORTANT: This script is meant to be run via the `upgrade` service defined in
# docker-compose.control.yml. The stack must be DOWN before running it, because
# it rewrites the contents of the PGDATA volume.

set -e
set -o pipefail

CURRENT_PGVERSION="$(cat $PGDATA/PG_VERSION)"
NEW_PGVERSION="13"
CURRENT_PGVERSION="$(cat "$PGDATA/PG_VERSION")"
# Use the major version of the postgres binaries baked into this image.
NEW_PGVERSION="$(pg_ctl --version | sed -E 's/.* ([0-9]+).*/\1/')"
PGWORKDIR=${PGWORKDIR:=/var/lib/postgresql/work}

echo "Detected current PGDATA version: ${CURRENT_PGVERSION}"
echo "Control-plane image PG version: ${NEW_PGVERSION}"

if [ ! "$CURRENT_PGVERSION" -lt "$NEW_PGVERSION" ]; then
echo "Current PG version is higher or equal to the PG version to be installed ($CURRENT_PGVERSION > $NEW_PGVERSION). Ignoring."
echo "Current PG version is already >= target version (${CURRENT_PGVERSION} >= ${NEW_PGVERSION}). Nothing to do."
exit 0
fi

Expand All @@ -16,17 +28,31 @@ export PGBINNEW="/usr/lib/postgresql/$NEW_PGVERSION/bin"
export PGDATAOLD="$PGDATA"
export PGDATANEW="$PGWORKDIR/datanew"

if [ ! -d "$PGBINOLD" ] || [ ! -x "$PGBINOLD/pg_ctl" ]; then
echo "ERROR: old cluster binaries not found at $PGBINOLD" >&2
echo "The control image only contains PG ${NEW_PGVERSION}. You must build or use an image that also ships PG ${CURRENT_PGVERSION} binaries." >&2
exit 1
fi

rm -rf "$PGWORKDIR" && mkdir -p "$PGWORKDIR" "$PGDATANEW"
chown -R postgres.postgres "$PGDATA" "$PGWORKDIR"
chown -R postgres:postgres "$PGDATA" "$PGWORKDIR"
cd "$PGWORKDIR"
# initialize new db

# initialize new db cluster
echo "Initializing new PostgreSQL ${NEW_PGVERSION} cluster..."
su -m postgres -c "$PGBINNEW/initdb --pgdata=$PGDATANEW --encoding=unicode --auth=trust"
echo "Performing a dry-run migration to PostgreSQL $NEW_PGVERSION..."
su -m postgres -c "$PGBINNEW/pg_upgrade -c"
echo "Performing the real migration to PostgreSQL $NEW_VERSION..."
su -m postgres -c "$PGBINNEW/pg_upgrade"

echo "Performing a dry-run migration to PostgreSQL ${NEW_PGVERSION}..."
su -m postgres -c "$PGBINNEW/pg_upgrade --old-datadir=$PGDATAOLD --new-datadir=$PGDATANEW --old-bindir=$PGBINOLD --new-bindir=$PGBINNEW -c"

echo "Performing the real migration to PostgreSQL ${NEW_PGVERSION}..."
su -m postgres -c "$PGBINNEW/pg_upgrade --old-datadir=$PGDATAOLD --new-datadir=$PGDATANEW --old-bindir=$PGBINOLD --new-bindir=$PGBINNEW"

echo "Replacing old cluster data with upgraded cluster..."
su -m postgres -c "rm -rf $PGDATAOLD/* && mv $PGDATANEW/* $PGDATAOLD/"
# as per docker hub documentation

# as per docker hub documentation, ensure remote container access still works
su -m postgres -c "echo \"listen_addresses = '*'\" >> $PGDATAOLD/postgresql.conf"
su -m postgres -c "echo \"host all all all md5\" >> $PGDATAOLD/pg_hba.conf"

echo "DONE"
Loading