From 7c4f0a72fe678e401f9a9a5d88815bba2c1de4a5 Mon Sep 17 00:00:00 2001 From: Mateusz Krajewski Date: Sun, 10 May 2026 12:25:31 +0200 Subject: [PATCH 1/4] chore: add docker packages for easier deployment --- .env.sample | 8 +++- .github/workflows/cypress-tests.yml | 2 +- .github/workflows/docker-publish.yml | 61 ++++++++++++++++++++++++++++ docker-compose.yml | 12 +++++- 4 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/docker-publish.yml diff --git a/.env.sample b/.env.sample index 2ff3bc361..9f24ee28a 100644 --- a/.env.sample +++ b/.env.sample @@ -6,4 +6,10 @@ MYSQL_HOST=db MYSQL_PORT=3306 BASE_LOCATOR=IO91WM WEBSITE_URL=http://localhost -DIRECTORY=/var/www/html \ No newline at end of file +DIRECTORY=/var/www/html + +# Docker images (GitHub Container Registry). Override for forks, e.g. ghcr.io/yourusername +# CLOUDLOG_IMAGE_REGISTRY=ghcr.io/magicbug +# CLOUDLOG_IMAGE_TAG=latest +# +# Compose: development uses docker-compose.yml (default); production uses docker-compose.prod.yml diff --git a/.github/workflows/cypress-tests.yml b/.github/workflows/cypress-tests.yml index 0436c50ba..b5ac925a5 100644 --- a/.github/workflows/cypress-tests.yml +++ b/.github/workflows/cypress-tests.yml @@ -48,7 +48,7 @@ jobs: sudo chmod +x /usr/local/bin/docker-compose - name: Build Docker services - run: docker-compose up -d + run: docker-compose up -d --build - name: Wait for services to start run: | diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 000000000..4adbd6f5b --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,61 @@ +name: Publish Docker images + +on: + push: + tags: + workflow_dispatch: + +permissions: + contents: read + packages: write + +jobs: + build-and-push: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - dockerfile: Dockerfile + image: cloudlog-web + - dockerfile: Dockerfile-db + image: cloudlog-db + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository_owner }}/${{ matrix.image }} + tags: | + type=semver,pattern={{version}} + type=sha + type=raw,value=latest,enable=${{ github.ref_type == 'tag' }} + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + file: ${{ matrix.dockerfile }} + push: true + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/docker-compose.yml b/docker-compose.yml index 96d55ec26..95f440248 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,9 +1,18 @@ +# Development stack — local code, optional local image build. +# docker compose up -d +# docker compose build && docker compose up -d # without registry images +# +# Production: use docker-compose.prod.yml + networks: mynet: services: web: - build: . + image: ghcr.io/magicbug/cloudlog-web:latest + build: + context: . + dockerfile: Dockerfile env_file: - .env ports: @@ -19,6 +28,7 @@ services: restart: on-failure db: + image: ghcr.io/magicbug/cloudlog-db:latest build: context: . dockerfile: Dockerfile-db From 1691f3bdbb262b06b55d40b36f8b15e028396b6d Mon Sep 17 00:00:00 2001 From: Mateusz Krajewski Date: Sun, 10 May 2026 12:44:38 +0200 Subject: [PATCH 2/4] refactor: optimize docker image --- .dockerignore | 28 ++++++++++++++++++++++++++++ Dockerfile | 47 +++++++++++++++++++---------------------------- 2 files changed, 47 insertions(+), 28 deletions(-) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..1ad6e1440 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,28 @@ +# Slim context: web image uses script.sh only; db image uses install/assets/install.sql — exclude bulky dirs. + +.git +.github + +application/ +system/ +src/ +assets/ +images/ +updates/ +uploads/ +backup/ +cypress/ + +node_modules +**/node_modules + +.vscode/ +.idea/ + +*.md +package.json +package-lock.json +cypress.config.js + +.env +.env.* diff --git a/Dockerfile b/Dockerfile index c3c516f8c..3720c3156 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,39 +1,30 @@ -# Use the official image for PHP and Apache FROM php:7.4-apache -# Set the working directory to /var/www/html WORKDIR /var/www/html -# Install system dependencies, including git and libxml2 -RUN apt-get update && apt-get install -y \ - libcurl4-openssl-dev \ - libxml2-dev \ - libzip-dev \ - zlib1g-dev \ - libpng-dev \ - libonig-dev \ - default-mysql-client \ - curl \ - && apt-get clean \ +# Single layer: APT cleanup + PHP extensions (curl used by application PHP code) +RUN apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + libcurl4-openssl-dev \ + libxml2-dev \ + libzip-dev \ + zlib1g-dev \ + libpng-dev \ + libonig-dev \ + default-mysql-client \ && rm -rf /var/lib/apt/lists/* \ - && docker-php-ext-install pdo_mysql \ - && docker-php-ext-install mysqli \ - && docker-php-ext-install gd \ - && docker-php-ext-install mbstring \ - && docker-php-ext-install zip \ - && docker-php-ext-install xml \ + && docker-php-ext-install -j"$(nproc)" curl pdo_mysql mysqli gd mbstring zip xml \ && a2enmod rewrite -# Copy script.sh and make it executable COPY script.sh /usr/local/bin/startup.sh RUN sed -i 's/\r$//' /usr/local/bin/startup.sh && chmod +x /usr/local/bin/startup.sh -# Configure PHP for larger file uploads (30MB) -RUN echo "upload_max_filesize = 30M" >> /usr/local/etc/php/conf.d/uploads.ini \ - && echo "post_max_size = 35M" >> /usr/local/etc/php/conf.d/uploads.ini \ - && echo "memory_limit = 64M" >> /usr/local/etc/php/conf.d/uploads.ini \ - && echo "max_execution_time = 300" >> /usr/local/etc/php/conf.d/uploads.ini \ - && echo "max_input_time = 300" >> /usr/local/etc/php/conf.d/uploads.ini +RUN printf '%s\n' \ + 'upload_max_filesize = 30M' \ + 'post_max_size = 35M' \ + 'memory_limit = 64M' \ + 'max_execution_time = 300' \ + 'max_input_time = 300' \ + > /usr/local/etc/php/conf.d/uploads.ini -# Expose port 80 -EXPOSE 80 \ No newline at end of file +EXPOSE 80 From 999133efaeebf9144355c3a82eb8a15a5a0a5920 Mon Sep 17 00:00:00 2001 From: Mateusz Krajewski Date: Sun, 10 May 2026 13:17:12 +0200 Subject: [PATCH 3/4] fix: fix previous issue with unused .env in dockerfile --- .dockerignore | 14 ++--- Dockerfile | 5 ++ Dockerfile-db | 10 ++-- docker-compose.yml | 11 +--- script.sh | 126 +++++++++++++++++++++++++++++---------------- 5 files changed, 99 insertions(+), 67 deletions(-) diff --git a/.dockerignore b/.dockerignore index 1ad6e1440..f10966d69 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,21 +1,13 @@ -# Slim context: web image uses script.sh only; db image uses install/assets/install.sql — exclude bulky dirs. +# Keep image context small where safe; application source must NOT be excluded. .git .github -application/ -system/ -src/ -assets/ -images/ -updates/ -uploads/ -backup/ -cypress/ - node_modules **/node_modules +cypress + .vscode/ .idea/ diff --git a/Dockerfile b/Dockerfile index 3720c3156..87d4396b4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,6 +16,11 @@ RUN apt-get update \ && docker-php-ext-install -j"$(nproc)" curl pdo_mysql mysqli gd mbstring zip xml \ && a2enmod rewrite +COPY . /var/www/html/ + +# Duplicate for seeding an empty Docker volume mounted over /var/www/html (Compose cloudlog_html) +RUN cp -a /var/www/html /usr/local/share/cloudlog-stock + COPY script.sh /usr/local/bin/startup.sh RUN sed -i 's/\r$//' /usr/local/bin/startup.sh && chmod +x /usr/local/bin/startup.sh diff --git a/Dockerfile-db b/Dockerfile-db index e4ce0ce88..bdbd23e91 100644 --- a/Dockerfile-db +++ b/Dockerfile-db @@ -4,9 +4,13 @@ FROM mariadb:latest # Add the install.sql file to the docker image ADD install/assets/install.sql /docker-entrypoint-initdb.d -# Create a healthcheck script that uses mariadb-admin -RUN echo '#!/bin/bash\nmariadb-admin ping -h "localhost" --silent' > /usr/local/bin/healthcheck.sh \ +# Healthcheck: must use DB credentials (MariaDB 12 rejects anonymous/root-without-password probes → log spam) +RUN printf '%s\n' \ + '#!/bin/bash' \ + 'set -e' \ + 'exec mariadb-admin ping -h localhost --silent -u"${MYSQL_USER}" -p"${MYSQL_PASSWORD}"' \ + > /usr/local/bin/healthcheck.sh \ && chmod +x /usr/local/bin/healthcheck.sh # Expose port 3306 -EXPOSE 3306 \ No newline at end of file +EXPOSE 3306 diff --git a/docker-compose.yml b/docker-compose.yml index 95f440248..e9a129dad 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,3 @@ -# Development stack — local code, optional local image build. -# docker compose up -d -# docker compose build && docker compose up -d # without registry images -# -# Production: use docker-compose.prod.yml networks: mynet: @@ -18,7 +13,7 @@ services: ports: - "80:80" volumes: - - ./:/var/www/html:rw + - ./web:/var/www/html command: ["/usr/local/bin/startup.sh"] depends_on: db: @@ -35,7 +30,7 @@ services: env_file: - .env volumes: - - db_data:/var/lib/mysql + - ./db_data:/var/lib/mysql networks: - mynet healthcheck: @@ -45,5 +40,3 @@ services: interval: 10s start_period: 40s -volumes: - db_data: {} diff --git a/script.sh b/script.sh index 4a16f7bc0..66f10536a 100755 --- a/script.sh +++ b/script.sh @@ -1,24 +1,34 @@ #!/bin/bash -# Define the file path for .env and the file to modify -if [ -f "./.env" ]; then - ENV_FILE="./.env" -else - ENV_FILE="./.env.sample" +# App root (Dockerfile WORKDIR); ensures ./.env resolves even if the process cwd differs +cd /var/www/html || exit 1 + +# Named volume on /var/www/html starts empty and hides image layers — copy stock tree once. +if [ ! -f "index.php" ] && [ -d "/usr/local/share/cloudlog-stock" ]; then + echo "Copying Cloudlog into /var/www/html from image (first run or empty volume)..." + cp -a /usr/local/share/cloudlog-stock/. /var/www/html/ fi + CONFIG_FILE="install/config/config.php" DATABASE_FILE="install/config/database.php" DEST_DIR="application/config" -# Check if .env file exists -if [ ! -f "$ENV_FILE" ]; then - echo ".env file not found!" +# Load variables: prefer files on the bind mount; otherwise use env already injected by Compose (env_file) +if [ -f "./.env" ]; then + # shellcheck disable=SC1091 + source "./.env" +elif [ -f "./.env.sample" ]; then + # shellcheck disable=SC1091 + source "./.env.sample" +elif [ -n "${MYSQL_DATABASE:-}" ] && [ -n "${MYSQL_USER:-}" ] && [ -n "${MYSQL_PASSWORD:-}" ] && \ + [ -n "${MYSQL_HOST:-}" ] && [ -n "${BASE_LOCATOR:-}" ] && [ -n "${WEBSITE_URL:-}" ] && [ -n "${DIRECTORY:-}" ]; then + : +else + echo ".env file not found under /var/www/html and required variables are not set in the environment." + echo "Add .env next to docker-compose.yml (bind-mounted as /var/www/html) or pass variables via Compose env_file / environment." exit 1 fi -# Read the .env file -source $ENV_FILE - # Check if MYSQL_DATABASE is set if [ -z "${MYSQL_DATABASE}" ]; then echo "MYSQL_DATABASE is not set in .env file!" @@ -55,17 +65,30 @@ if [ -z "${DIRECTORY}" ]; then exit 1 fi +# Normalize filesystem root (avoids //var/www/html when DIRECTORY is already absolute) +APP_ROOT="${DIRECTORY%/}" +case "$APP_ROOT" in + /*) ;; + *) APP_ROOT="/${APP_ROOT}" ;; +esac + # Check if destination directory exists, if not create it if [ ! -d "$DEST_DIR" ]; then - mkdir -p $DEST_DIR + mkdir -p "$DEST_DIR" fi # Check if configuration has already been processed if [ -f "$DEST_DIR/database.php" ] && [ -f "$DEST_DIR/config.php" ]; then echo "Configuration files already exist, skipping processing..." else + if [ ! -f "$DATABASE_FILE" ] || [ ! -f "$CONFIG_FILE" ]; then + echo "ERROR: Installer templates missing under /var/www/html (install/config/)." + echo "Rebuild the web image so it includes the full Cloudlog tree, or bind-mount a git checkout." + exit 1 + fi + echo "Processing configuration files..." - + # Use sed with a different delimiter (`|`) to avoid conflicts with special characters # Strip any trailing whitespace/newlines from variables before using them CLEAN_DATABASE=$(echo "${MYSQL_DATABASE}" | tr -d '\r\n') @@ -75,21 +98,21 @@ else CLEAN_LOCATOR=$(echo "${BASE_LOCATOR}" | tr -d '\r\n') CLEAN_URL=$(echo "${WEBSITE_URL}" | tr -d '\r\n') CLEAN_DIRECTORY=$(echo "${DIRECTORY}" | tr -d '\r\n') - - sed -i "s|%DATABASE%|${CLEAN_DATABASE}|g" $DATABASE_FILE - sed -i "s|%USERNAME%|${CLEAN_USER}|g" $DATABASE_FILE - sed -i "s|%PASSWORD%|${CLEAN_PASSWORD}|g" $DATABASE_FILE - sed -i "s|%HOSTNAME%|${CLEAN_HOST}|g" $DATABASE_FILE - sed -i "s|%baselocator%|${CLEAN_LOCATOR}|g" $CONFIG_FILE - sed -i "s|%websiteurl%|${CLEAN_URL}|g" $CONFIG_FILE - sed -i "s|%directory%|${CLEAN_DIRECTORY}|g" $CONFIG_FILE + + sed -i "s|%DATABASE%|${CLEAN_DATABASE}|g" "$DATABASE_FILE" + sed -i "s|%USERNAME%|${CLEAN_USER}|g" "$DATABASE_FILE" + sed -i "s|%PASSWORD%|${CLEAN_PASSWORD}|g" "$DATABASE_FILE" + sed -i "s|%HOSTNAME%|${CLEAN_HOST}|g" "$DATABASE_FILE" + sed -i "s|%baselocator%|${CLEAN_LOCATOR}|g" "$CONFIG_FILE" + sed -i "s|%websiteurl%|${CLEAN_URL}|g" "$CONFIG_FILE" + sed -i "s|%directory%|${CLEAN_DIRECTORY}|g" "$CONFIG_FILE" # Move the files to the destination directory - mv $CONFIG_FILE $DEST_DIR - mv $DATABASE_FILE $DEST_DIR + mv "$CONFIG_FILE" "$DEST_DIR" + mv "$DATABASE_FILE" "$DEST_DIR" - # Delete the /install directory - rm -rf /install + # Remove installer templates from the bind-mounted tree (under app root, not filesystem /install) + rm -rf ./install fi echo "Replacement complete." @@ -106,24 +129,39 @@ else echo "Database connection failed, but continuing anyway since healthcheck passed..." fi -# Set Permissions -chown -R root:www-data /${DIRECTORY}/application/config/ -chown -R root:www-data /${DIRECTORY}/application/logs/ -chown -R root:www-data /${DIRECTORY}/assets/qslcard/ -chown -R root:www-data /${DIRECTORY}/backup/ -chown -R root:www-data /${DIRECTORY}/updates/ -chown -R root:www-data /${DIRECTORY}/uploads/ -chown -R root:www-data /${DIRECTORY}/images/eqsl_card_images/ -chown -R root:www-data /${DIRECTORY}/assets/json - -chmod -R g+rw /${DIRECTORY}/application/config/ -chmod -R g+rw /${DIRECTORY}/application/logs/ -chmod -R g+rw /${DIRECTORY}/assets/qslcard/ -chmod -R g+rw /${DIRECTORY}/backup/ -chmod -R g+rw /${DIRECTORY}/updates/ -chmod -R g+rw /${DIRECTORY}/uploads/ -chmod -R g+rw /${DIRECTORY}/images/eqsl_card_images/ -chmod -R g+rw /${DIRECTORY}/assets/json +# Writable paths (create if missing, then fix ownership) +mkdir -p \ + "${APP_ROOT}/application/config" \ + "${APP_ROOT}/application/cache" \ + "${APP_ROOT}/application/logs" \ + "${APP_ROOT}/assets/qslcard" \ + "${APP_ROOT}/backup" \ + "${APP_ROOT}/updates" \ + "${APP_ROOT}/uploads" \ + "${APP_ROOT}/images/eqsl_card_images" \ + "${APP_ROOT}/assets/json" + +chown -R root:www-data \ + "${APP_ROOT}/application/config" \ + "${APP_ROOT}/application/cache" \ + "${APP_ROOT}/application/logs" \ + "${APP_ROOT}/assets/qslcard" \ + "${APP_ROOT}/backup" \ + "${APP_ROOT}/updates" \ + "${APP_ROOT}/uploads" \ + "${APP_ROOT}/images/eqsl_card_images" \ + "${APP_ROOT}/assets/json" + +chmod -R g+rw \ + "${APP_ROOT}/application/config" \ + "${APP_ROOT}/application/cache" \ + "${APP_ROOT}/application/logs" \ + "${APP_ROOT}/assets/qslcard" \ + "${APP_ROOT}/backup" \ + "${APP_ROOT}/updates" \ + "${APP_ROOT}/uploads" \ + "${APP_ROOT}/images/eqsl_card_images" \ + "${APP_ROOT}/assets/json" # Start Apache in the foreground -exec apache2-foreground \ No newline at end of file +exec apache2-foreground From 860cd5e01c816825dbb8f9e7f065a083739e3f2b Mon Sep 17 00:00:00 2001 From: Mateusz Krajewski Date: Sun, 10 May 2026 14:53:54 +0200 Subject: [PATCH 4/4] feat: add parameter to change development/production to env --- .dockerignore | 20 -------------------- .env.sample | 3 +++ index.php | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 47 insertions(+), 22 deletions(-) delete mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index f10966d69..000000000 --- a/.dockerignore +++ /dev/null @@ -1,20 +0,0 @@ -# Keep image context small where safe; application source must NOT be excluded. - -.git -.github - -node_modules -**/node_modules - -cypress - -.vscode/ -.idea/ - -*.md -package.json -package-lock.json -cypress.config.js - -.env -.env.* diff --git a/.env.sample b/.env.sample index 9f24ee28a..0c9c184a3 100644 --- a/.env.sample +++ b/.env.sample @@ -1,3 +1,6 @@ +# CodeIgniter environment: development | testing | production (developer UI / error display) +CLOUDLOG_ENV=development + MYSQL_ROOT_PASSWORD=rootpassword MYSQL_DATABASE=cloudlog MYSQL_USER=cloudlog diff --git a/index.php b/index.php index 7d8fe56dd..5d7ff2fd7 100644 --- a/index.php +++ b/index.php @@ -36,6 +36,40 @@ * @filesource */ +/* + *--------------------------------------------------------------- + * Optional .env next to this file (existing getenv/$_SERVER values are not overwritten — Docker/OS wins) + *--------------------------------------------------------------- + */ + $cloudlogEnvFile = __DIR__ . DIRECTORY_SEPARATOR . '.env'; + if (is_readable($cloudlogEnvFile)) { + foreach (file($cloudlogEnvFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) { + $line = trim($line); + if ($line === '' || (isset($line[0]) && $line[0] === '#')) { + continue; + } + $eq = strpos($line, '='); + if ($eq === false) { + continue; + } + $name = trim(substr($line, 0, $eq)); + $value = trim(substr($line, $eq + 1)); + if ($name === '') { + continue; + } + if (strlen($value) >= 2 + && (($value[0] === '"' && substr($value, -1) === '"') + || ($value[0] === "'" && substr($value, -1) === "'"))) { + $value = substr($value, 1, -1); + } + if (getenv($name) === false) { + putenv($name . '=' . $value); + $_ENV[$name] = $value; + $_SERVER[$name] = $value; + } + } + } + /* *--------------------------------------------------------------- * APPLICATION ENVIRONMENT @@ -51,10 +85,18 @@ * testing * production * + * Set CLOUDLOG_ENV or CI_ENV in .env (or in the server environment). Defaults to development. + * * NOTE: If you change these, also change the error_reporting() code below */ - #define('ENVIRONMENT', isset($_SERVER['CI_ENV']) ? $_SERVER['CI_ENV'] : 'development'); - define('ENVIRONMENT', 'development'); + $cloudlog_environment = getenv('CLOUDLOG_ENV'); + if ($cloudlog_environment === false || $cloudlog_environment === '') { + $cloudlog_environment = getenv('CI_ENV'); + } + if ($cloudlog_environment === false || $cloudlog_environment === '') { + $cloudlog_environment = 'development'; + } + define('ENVIRONMENT', $cloudlog_environment); /* *---------------------------------------------------------------