From da9af888c0c3963d6e43ccafb0546ec7533d2c94 Mon Sep 17 00:00:00 2001 From: boris Date: Fri, 28 Nov 2025 02:11:23 +0000 Subject: [PATCH] Attempt to be prod ready --- .gitea/workflows/ci.yml | 148 ++++++++++++--------- .github/workflows/ci.yml | 102 +++++++++++++++ README.md | 41 ++++++ docker/php/Dockerfile | 246 +++++++++++++++++++---------------- docker/prod/entrypoint.sh | 19 +++ docker/prod/nginx.conf | 29 +++++ docker/prod/supervisord.conf | 21 +++ 7 files changed, 434 insertions(+), 172 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100755 docker/prod/entrypoint.sh create mode 100644 docker/prod/nginx.conf create mode 100644 docker/prod/supervisord.conf diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 583873b..5327314 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -1,20 +1,74 @@ -name: CI - Build Tonehaus Docker image +name: CI (Gitea) on: push: - branches: [ main ] + branches: + - main + - prod pull_request: + branches: + - main + - prod workflow_dispatch: env: - IMAGE_NAME: tonehaus + APP_ENV: test + APP_SECRET: ci-secret + DATABASE_DRIVER: sqlite + DATABASE_SQLITE_PATH: ${{ gitea.workspace }}/var/data/database.test.sqlite DOCKERFILE: docker/php/Dockerfile BUILD_TARGET: prod - PLATFORMS: linux/amd64 + IMAGE_NAME: tonehaus-app jobs: - tonehaus-ci-build: + php-tests: runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + extensions: intl, mbstring, pdo_pgsql, pdo_sqlite, zip, gd + coverage: none + ini-values: memory_limit=512M + tools: composer:v2 + + - name: Validate Composer manifest + run: composer validate --strict + + - name: Cache Composer downloads + uses: actions/cache@v4 + with: + path: | + ~/.cache/composer/files + ~/.cache/composer/vcs + key: composer-${{ runner.os }}-${{ hashFiles('**/composer.lock') }} + restore-keys: | + composer-${{ runner.os }}- + + - name: Install Composer dependencies + run: composer install --prefer-dist --no-interaction --no-progress + + - name: Prepare SQLite database + run: | + mkdir -p "$(dirname "$DATABASE_SQLITE_PATH")" + touch "$DATABASE_SQLITE_PATH" + php bin/console doctrine:migrations:migrate --no-interaction --allow-no-migration + + - name: Run PHPUnit + run: vendor/bin/phpunit --colors=always + + docker-image: + needs: php-tests + runs-on: ubuntu-latest + env: + REGISTRY: ${{ secrets.REGISTRY }} + REGISTRY_IMAGE: ${{ secrets.REGISTRY_IMAGE }} + REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }} + REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} steps: - name: Checkout uses: actions/checkout@v4 @@ -22,62 +76,40 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Compute tags - id: meta - run: | - SHA="${GITHUB_SHA:-${GITEA_SHA:-unknown}}" - SHORT_SHA="${SHA:0:7}" - echo "short_sha=$SHORT_SHA" >> "$GITHUB_OUTPUT" + - name: Build prod image (local) + uses: docker/build-push-action@v6 + with: + context: . + file: ${{ env.DOCKERFILE }} + target: ${{ env.BUILD_TARGET }} + tags: ${{ env.IMAGE_NAME }}:ci + load: true - - name: Optional registry login + - name: Verify baked APP_ENV + run: docker run --rm --entrypoint sh ${{ env.IMAGE_NAME }}:ci -c 'test "$APP_ENV" = "prod"' + + - name: Verify Symfony artifacts exist + run: | + docker run --rm --entrypoint sh ${{ env.IMAGE_NAME }}:ci -c 'test -f /var/www/html/public/index.php' + docker run --rm --entrypoint sh ${{ env.IMAGE_NAME }}:ci -c 'test -f /var/www/html/bin/console' + + - name: Smoke-test entrypoint & migrations + run: docker run --rm --entrypoint /entrypoint.sh ${{ env.IMAGE_NAME }}:ci true + + - name: Login to registry if: ${{ env.REGISTRY != '' && env.REGISTRY_USERNAME != '' && env.REGISTRY_PASSWORD != '' }} - env: - REGISTRY: ${{ secrets.REGISTRY }} - REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }} - REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} run: | echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY" -u "$REGISTRY_USERNAME" --password-stdin - - name: Docker Build - if: ${{ env.REGISTRY != '' && env.REGISTRY_IMAGE != '' }} - env: - REGISTRY: ${{ secrets.REGISTRY }} - REGISTRY_IMAGE: ${{ secrets.REGISTRY_IMAGE }} - run: | - TAG_SHA=${{ steps.meta.outputs.short_sha }} - docker buildx build \ - --platform "$PLATFORMS" \ - --file "$DOCKERFILE" \ - --target "$BUILD_TARGET" \ - --build-arg APP_ENV=prod \ - --tag "$REGISTRY/$REGISTRY_IMAGE:$TAG_SHA" \ - --tag "$REGISTRY/$REGISTRY_IMAGE:ci" \ - --push \ - . - - # - name: Build single-arch images for artifacts (no registry) - # if: ${{ env.REGISTRY == '' }} - # run: | - # TAG_SHA=${{ steps.meta.outputs.short_sha }} - # for P in $PLATFORMS; do \ - # ARCH=${P#linux/}; \ - # docker buildx build \ - # --platform "$P" \ - # --file "$DOCKERFILE" \ - # --target "$BUILD_TARGET" \ - # --build-arg APP_ENV=prod \ - # --output type=docker \ - # --tag "$IMAGE_NAME:$TAG_SHA-$ARCH" \ - # . ; \ - # docker save "$IMAGE_NAME:$TAG_SHA-$ARCH" -o "tonehaus-image-$ARCH.tar" ; \ - # done - -## Artifacts not configured yet.. -# - name: Upload artifacts -# if: ${{ env.REGISTRY == '' }} -# uses: actions/upload-artifact@v4 -# with: -# name: tonehaus-images -# path: | -# tonehaus-image-amd64.tar + - name: Push prod image + if: ${{ env.REGISTRY != '' && env.REGISTRY_IMAGE != '' && env.REGISTRY_USERNAME != '' && env.REGISTRY_PASSWORD != '' }} + uses: docker/build-push-action@v6 + with: + context: . + file: ${{ env.DOCKERFILE }} + target: ${{ env.BUILD_TARGET }} + push: true + tags: | + ${{ env.REGISTRY }}/${{ env.REGISTRY_IMAGE }}:ci + ${{ env.REGISTRY }}/${{ env.REGISTRY_IMAGE }}:${{ github.sha }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..75b8e6a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,102 @@ +name: CI + +on: + push: + branches: + - main + - prod + pull_request: + branches: + - main + - prod + +permissions: + contents: read + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +env: + APP_ENV: test + APP_SECRET: ci-secret + DATABASE_DRIVER: sqlite + DATABASE_SQLITE_PATH: ${{ github.workspace }}/var/data/database.test.sqlite + +jobs: + php-tests: + name: PHPUnit + migrations + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + extensions: intl, mbstring, pdo_pgsql, pdo_sqlite, zip, gd + coverage: none + ini-values: memory_limit=512M + tools: composer:v2 + + - name: Validate Composer manifest + run: composer validate --strict + + - name: Cache Composer downloads + uses: actions/cache@v4 + with: + path: | + ~/.cache/composer/files + ~/.cache/composer/vcs + key: composer-${{ runner.os }}-${{ hashFiles('**/composer.lock') }} + restore-keys: | + composer-${{ runner.os }}- + + - name: Install Composer dependencies + run: composer install --prefer-dist --no-interaction --no-progress + + - name: Prepare SQLite database + run: | + mkdir -p "$(dirname "$DATABASE_SQLITE_PATH")" + touch "$DATABASE_SQLITE_PATH" + php bin/console doctrine:migrations:migrate --no-interaction --allow-no-migration + + - name: Run PHPUnit + run: vendor/bin/phpunit --colors=always + + docker-image: + name: Build production image + needs: php-tests + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build prod image + id: build-prod + uses: docker/build-push-action@v6 + with: + context: . + file: docker/php/Dockerfile + target: prod + load: true + push: false + tags: tonehaus-app:ci + + - name: Verify baked APP_ENV + run: docker run --rm --entrypoint sh tonehaus-app:ci -c 'test "$APP_ENV" = "prod"' + + - name: Verify Symfony artifacts exist + run: | + docker run --rm --entrypoint sh tonehaus-app:ci -c 'test -f /var/www/html/public/index.php' + docker run --rm --entrypoint sh tonehaus-app:ci -c 'test -f /var/www/html/bin/console' + + - name: Smoke-test entrypoint & migrations + run: docker run --rm --entrypoint /entrypoint.sh tonehaus-app:ci true + diff --git a/README.md b/README.md index 0eb3cf1..583c233 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,47 @@ docker compose exec php php bin/console app:seed-demo-albums --count=40 --attach docker compose exec php php bin/console app:seed-demo-reviews --cover-percent=70 --max-per-album=8 ``` +## Production container (immutable) + +The repository ships with a single-container production target that bundles PHP-FPM, Nginx, your code, and a self-contained SQLite database. The build bakes in the `APP_ENV=prod` flag so production-only Symfony config is used automatically, and no bind mounts are required at runtime. + +1. Build the image (uses `docker/php/Dockerfile`'s `prod` stage): + +```bash +docker build \ + --target=prod \ + -t tonehaus-app:latest \ + -f docker/php/Dockerfile \ + . +``` + +2. Run the container (listens on port 8080 inside the container): + +```bash +docker run -d \ + --name tonehaus \ + -p 8080:8080 \ + -e APP_ENV=prod \ + -e APP_SECRET=change_me \ + -e SPOTIFY_CLIENT_ID=your_client_id \ + -e SPOTIFY_CLIENT_SECRET=your_client_secret \ + tonehaus-app:latest +``` + + - The runtime defaults to `DATABASE_DRIVER=sqlite` and stores the database file inside the image at `var/data/database.sqlite`. On each boot the entrypoint runs Doctrine migrations (safe to re-run) so the schema stays current while the container filesystem remains immutable from the host's perspective. + - To point at Postgres (or any external database), override `DATABASE_DRIVER` and `DATABASE_URL` at `docker run` time and optionally disable auto-migration with `RUN_MIGRATIONS_ON_START=0`. + - Health endpoint: `GET /healthz` on the published port (example: `curl http://localhost:8080/healthz`). + +3. Rebuild/redeploy by re-running the `docker build` command; no manual steps or bind mounts are involved. + +## Continuous integration + +- `.github/workflows/ci.yml` runs on pushes and pull requests targeting `main` or `prod`. +- Job 1 installs Composer deps, prepares a SQLite database, runs Doctrine migrations, and executes the PHPUnit suite under PHP 8.2 so functional regressions are caught early. +- Job 2 builds the production Docker image (`docker/php/Dockerfile` prod stage), checks that key Symfony artifacts (e.g., `public/index.php`, `bin/console`) are present, ensures `APP_ENV=prod` is baked in, and smoke-tests the `/entrypoint.sh` startup path. +- The resulting artifact mirrors the immutable container described above, so a green CI run guarantees the repo can be deployed anywhere via `docker run`. +- Self-hosted runners can use `.gitea/workflows/ci.yml`, which mirrors the GitHub workflow but also supports optional registry pushes after the image passes the same verification steps. + ## Database driver - Set `DATABASE_DRIVER=postgres` (default) to keep using the Postgres 16 container defined in `docker-compose.yml`. diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile index aac2e75..a391943 100644 --- a/docker/php/Dockerfile +++ b/docker/php/Dockerfile @@ -1,118 +1,136 @@ -# FROM php:8.2-fpm - -# # Install dependencies -# RUN apt-get update && apt-get install -y \ -# git \ -# unzip \ -# libzip-dev \ -# libpng-dev \ -# libjpeg-dev \ -# libonig-dev \ -# libxml2-dev \ -# zip \ -# && docker-php-ext-install pdo pdo_mysql zip gd mbstring exif pcntl bcmath intl opcache - -# # Install Composer -# COPY --from=composer:2 /usr/bin/composer /usr/bin/composer - -# # Copy PHP config -# COPY docker/php/php.ini /usr/local/etc/php/ - -# WORKDIR /var/www/html - # ----------------------------------------------------------------------------- # Base PHP-FPM with Composer + Symfony-friendly extensions # ----------------------------------------------------------------------------- - FROM php:8.2-fpm-alpine AS base - WORKDIR /var/www/html - - # System dependencies - RUN apk add --no-cache \ - bash git unzip icu-dev libpng-dev libjpeg-turbo-dev libwebp-dev \ - libzip-dev oniguruma-dev libxml2-dev postgresql-dev zlib-dev - - # PHP extensions commonly used by Symfony - RUN docker-php-ext-configure gd --with-jpeg --with-webp \ - && docker-php-ext-install -j"$(nproc)" \ - intl \ - gd \ - pdo_pgsql \ - opcache \ - mbstring \ - zip \ - xml - - # Composer available in the running container (dev + prod) - COPY --from=composer:2.7 /usr/bin/composer /usr/bin/composer - - # Recommended PHP settings (tweak as needed) - RUN { \ - echo "memory_limit=512M"; \ - echo "upload_max_filesize=50M"; \ - echo "post_max_size=50M"; \ - echo "date.timezone=UTC"; \ - } > /usr/local/etc/php/conf.d/php-recommended.ini \ - && { \ - echo "opcache.enable=1"; \ - echo "opcache.enable_cli=1"; \ - echo "opcache.memory_consumption=256"; \ - echo "opcache.interned_strings_buffer=16"; \ - echo "opcache.max_accelerated_files=20000"; \ - echo "opcache.validate_timestamps=0"; \ - echo "opcache.jit=tracing"; \ - echo "opcache.jit_buffer_size=128M"; \ - } > /usr/local/etc/php/conf.d/opcache-recommended.ini - - # Small healthcheck file for Nginx - RUN mkdir -p public && printf "OK" > public/healthz - - # Ensure correct user - RUN addgroup -g 1000 app && adduser -D -G app -u 1000 app - # php-fpm uses www-data; keep both available - RUN chown -R www-data:www-data /var/www - - # ----------------------------------------------------------------------------- - # Development image (mount your code via docker-compose volumes) - # ----------------------------------------------------------------------------- - FROM base AS dev - ENV APP_ENV=dev - # Optional: enable Xdebug (uncomment to use) - # RUN apk add --no-cache $PHPIZE_DEPS \ - # && pecl install xdebug \ - # && docker-php-ext-enable xdebug \ - # && { \ - # echo "xdebug.mode=debug,develop"; \ - # echo "xdebug.client_host=host.docker.internal"; \ - # } > /usr/local/etc/php/conf.d/xdebug.ini - # Composer cache directory (faster installs inside container) - ENV COMPOSER_CACHE_DIR=/tmp/composer - CMD ["php-fpm"] - - # ----------------------------------------------------------------------------- - # Production image (copies your app + installs deps + warms cache) - # ----------------------------------------------------------------------------- - FROM base AS prod - ENV APP_ENV=prod - # Copy only manifests first (better layer caching); ignore if missing - COPY composer.json composer.lock* symfony.lock* ./ - # Install vendors (no scripts here; run later with console if needed) - RUN --mount=type=cache,target=/tmp/composer \ - if [ -f composer.json ]; then \ - composer install --no-dev --prefer-dist --no-interaction --no-progress --no-scripts; \ - fi - - # Copy the rest of the app - COPY . /var/www/html - - # If Symfony console exists, finalize install & warm cache - RUN if [ -f bin/console ]; then \ - set -ex; \ - composer dump-autoload --no-dev --optimize; \ - php bin/console cache:clear --no-warmup; \ - php bin/console cache:warmup; \ - mkdir -p var && chown -R www-data:www-data var; \ - fi - - USER www-data - CMD ["php-fpm"] +FROM php:8.2-fpm-alpine AS base + +ARG APP_ENV=dev +ENV APP_ENV=${APP_ENV} + +WORKDIR /var/www/html + +# System dependencies shared across images +RUN apk add --no-cache \ + bash \ + git \ + unzip \ + icu-dev \ + libpng-dev \ + libjpeg-turbo-dev \ + libwebp-dev \ + libzip-dev \ + oniguruma-dev \ + libxml2-dev \ + postgresql-dev \ + sqlite-dev \ + zlib-dev \ + su-exec + +# PHP extensions commonly used by Symfony (plus both Postgres + SQLite) +RUN docker-php-ext-configure gd --with-jpeg --with-webp \ + && docker-php-ext-install -j"$(nproc)" \ + intl \ + gd \ + pdo_pgsql \ + pdo_sqlite \ + opcache \ + mbstring \ + zip \ + xml + +# Composer available in every stage (dev + prod) +COPY --from=composer:2.7 /usr/bin/composer /usr/bin/composer + +# Recommended PHP settings (tweak as needed) +RUN { \ + echo "memory_limit=512M"; \ + echo "upload_max_filesize=50M"; \ + echo "post_max_size=50M"; \ + echo "date.timezone=UTC"; \ + } > /usr/local/etc/php/conf.d/php-recommended.ini \ + && { \ + echo "opcache.enable=1"; \ + echo "opcache.enable_cli=1"; \ + echo "opcache.memory_consumption=256"; \ + echo "opcache.interned_strings_buffer=16"; \ + echo "opcache.max_accelerated_files=20000"; \ + echo "opcache.validate_timestamps=0"; \ + echo "opcache.jit=tracing"; \ + echo "opcache.jit_buffer_size=128M"; \ + } > /usr/local/etc/php/conf.d/opcache-recommended.ini + +# Small healthcheck file for HTTP probes +RUN mkdir -p public && printf "OK" > public/healthz + +# Ensure unprivileged app user exists +RUN addgroup -g 1000 app && adduser -D -G app -u 1000 app \ + && chown -R www-data:www-data /var/www + +# ----------------------------------------------------------------------------- +# Development image (mount your code via docker-compose volumes) +# ----------------------------------------------------------------------------- +FROM base AS dev +ARG APP_ENV=dev +ENV APP_ENV=${APP_ENV} +ENV APP_DEBUG=1 + +# Optional: enable Xdebug by uncommenting below +# RUN apk add --no-cache $PHPIZE_DEPS \ +# && pecl install xdebug \ +# && docker-php-ext-enable xdebug \ +# && { \ +# echo "xdebug.mode=debug,develop"; \ +# echo "xdebug.client_host=host.docker.internal"; \ +# } > /usr/local/etc/php/conf.d/xdebug.ini + +ENV COMPOSER_CACHE_DIR=/tmp/composer +CMD ["php-fpm"] + +# ----------------------------------------------------------------------------- +# Production image (copies your app + installs deps + warms cache) +# ----------------------------------------------------------------------------- +FROM base AS prod +ARG APP_ENV=prod +ENV APP_ENV=${APP_ENV} +ENV APP_DEBUG=0 \ + DATABASE_DRIVER=sqlite \ + DATABASE_SQLITE_PATH=/var/www/html/var/data/database.sqlite \ + RUN_MIGRATIONS_ON_START=1 + +# Copy only composer manifests for layer caching +COPY composer.json composer.lock* symfony.lock* ./ + +# Install vendors (cached) +RUN --mount=type=cache,target=/tmp/composer \ + if [ -f composer.json ]; then \ + composer install --no-dev --prefer-dist --no-interaction --no-progress --no-scripts; \ + fi + +# Copy the rest of the app +COPY . /var/www/html + +# Finalize install & warm cache +RUN if [ -f bin/console ]; then \ + set -ex; \ + composer dump-autoload --no-dev --optimize; \ + php bin/console cache:clear --no-warmup; \ + php bin/console cache:warmup; \ + mkdir -p var var/data public/uploads; \ + chown -R www-data:www-data var public/uploads; \ + fi + +# Runtime web stack (nginx + supervisor) for a single immutable container +RUN apk add --no-cache nginx supervisor curl + +COPY docker/prod/nginx.conf /etc/nginx/http.d/default.conf +COPY docker/prod/supervisord.conf /etc/supervisor/conf.d/app.conf +COPY docker/prod/entrypoint.sh /entrypoint.sh + +RUN chmod +x /entrypoint.sh \ + && mkdir -p /run/nginx /var/log/supervisor \ + && chown -R www-data:www-data /var/www/html + +EXPOSE 8080 + +ENTRYPOINT ["/entrypoint.sh"] +CMD ["supervisord", "-c", "/etc/supervisor/conf.d/app.conf"] \ No newline at end of file diff --git a/docker/prod/entrypoint.sh b/docker/prod/entrypoint.sh new file mode 100755 index 0000000..5637ab3 --- /dev/null +++ b/docker/prod/entrypoint.sh @@ -0,0 +1,19 @@ +#!/bin/sh +set -eu + +if [ "${RUN_MIGRATIONS_ON_START:-1}" = "1" ] && [ -f bin/console ]; then + if [ "${DATABASE_DRIVER:-sqlite}" = "sqlite" ]; then + SQLITE_PATH="${DATABASE_SQLITE_PATH:-/var/www/html/var/data/database.sqlite}" + SQLITE_DIR=$(dirname "${SQLITE_PATH}") + mkdir -p "${SQLITE_DIR}" + if [ ! -f "${SQLITE_PATH}" ]; then + touch "${SQLITE_PATH}" + fi + chown -R www-data:www-data "${SQLITE_DIR}" + fi + + su-exec www-data php bin/console doctrine:migrations:migrate --no-interaction --allow-no-migration +fi + +exec "$@" + diff --git a/docker/prod/nginx.conf b/docker/prod/nginx.conf new file mode 100644 index 0000000..f607d29 --- /dev/null +++ b/docker/prod/nginx.conf @@ -0,0 +1,29 @@ +server { + listen 8080; + server_name _; + root /var/www/html/public; + + index index.php; + + location / { + try_files $uri /index.php$is_args$args; + } + + location ~ \.php$ { + include fastcgi_params; + fastcgi_pass 127.0.0.1:9000; + fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; + fastcgi_param DOCUMENT_ROOT $realpath_root; + internal; + } + + location = /healthz { + access_log off; + add_header Content-Type text/plain; + return 200 "OK"; + } + + error_log /var/log/nginx/error.log; + access_log /var/log/nginx/access.log; +} + diff --git a/docker/prod/supervisord.conf b/docker/prod/supervisord.conf new file mode 100644 index 0000000..2e7bd88 --- /dev/null +++ b/docker/prod/supervisord.conf @@ -0,0 +1,21 @@ +[supervisord] +nodaemon=true +logfile=/var/log/supervisor/supervisord.log +pidfile=/var/run/supervisord.pid + +[program:php-fpm] +command=/usr/local/sbin/php-fpm --nodaemonize +autorestart=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +[program:nginx] +command=/usr/sbin/nginx -g "daemon off;" +autorestart=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +