diff --git a/debian/Dockerfile b/debian/Dockerfile index 20f534b..e87abfd 100644 --- a/debian/Dockerfile +++ b/debian/Dockerfile @@ -1,16 +1,40 @@ +FROM composer:latest AS composer + +RUN curl -s "https://api.github.com/repos/invoiceninja/invoiceninja/releases/latest" | \ + grep -o '"browser_download_url": "[^"]*invoiceninja.tar"' | \ + cut -d '"' -f 4 | \ + xargs curl -sL | \ + tar -xz + +RUN ln -s ./resources/views/react/index.blade.php ./public/index.html + +# Set permissions: directories 755, files 644 +RUN chmod -R a=r,u+w,a+X . + +# Install dependencies +RUN composer install --no-dev --no-scripts --no-autoloader --ignore-platform-reqs + +RUN composer dump-autoload --optimize + +RUN php artisan storage:link + +# Octane +RUN php artisan octane:install --server=frankenphp + +# ================== +# InvoiceNinja image +# ================== FROM dunglas/frankenphp:1-php8.3-bookworm -ARG USER=ninja +ARG user=ninja # PHP modules ARG php_require="bcmath gd pdo_mysql zip" ARG php_suggest="exif imagick intl pcntl soap saxon-12.5.0" ARG php_extra="opcache" -ENV APP_DIR=/app - -# Create a system user -RUN useradd -r ${USER} +# Create a system user UID/GID=999 +RUN useradd -r ${user} # Allow to bind to privileged ports RUN setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/frankenphp @@ -26,19 +50,19 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ xfonts-wqy \ # Install google-chrome-stable(amd64)/chromium(arm64) && if [ "$(dpkg --print-architecture)" = "amd64" ]; then \ - mkdir -p /etc/apt/keyrings \ - && curl -fsSL https://dl.google.com/linux/linux_signing_key.pub | \ - gpg --dearmor -o /etc/apt/keyrings/google.gpg \ - && echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/google.gpg] https://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list \ - && apt-get update \ - && apt-get install -y --no-install-recommends google-chrome-stable \ - && mkdir /config/google-chrome \ - && chown ${USER}: /config/google-chrome; \ + mkdir -p /etc/apt/keyrings \ + && curl -fsSL https://dl.google.com/linux/linux_signing_key.pub | \ + gpg --dearmor -o /etc/apt/keyrings/google.gpg \ + && echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/google.gpg] https://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list \ + && apt-get update \ + && apt-get install -y --no-install-recommends google-chrome-stable \ + && mkdir /config/google-chrome \ + && chown ${user}: /config/google-chrome; \ elif [ "$(dpkg --print-architecture)" = "arm64" ]; then \ - apt-get install -y --no-install-recommends \ - chromium \ - && mkdir /config/chromium \ - && chown ${USER}: /config/chromium; \ + apt-get install -y --no-install-recommends \ + chromium \ + && mkdir /config/chromium \ + && chown ${user}: /config/chromium; \ fi \ # Cleanup && apt-get purge -y gpg \ @@ -48,46 +72,30 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ # Install PHP extensions RUN install-php-extensions \ - ${php_require} \ - ${php_suggest} \ - ${php_extra} \ - @composer + ${php_require} \ + ${php_suggest} \ + ${php_extra} # Configure PHP -RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" +RUN mv "${PHP_INI_DIR}/php.ini-production" "${PHP_INI_DIR}/php.ini" + +# Create directory for artisan tinker (init.sh) +RUN mkdir /config/psysh \ + && chown ${user}: /config/psysh + +# Change owner for caddy directories +RUN chown -R ${user}: \ + /data/caddy \ + /config/caddy + +ENTRYPOINT ["/usr/local/bin/init.sh"] + +CMD ["frankenphp", "php-cli", "artisan", "octane:frankenphp"] + +# InvoiceNinja +COPY --from=composer --chown=${user}:${user} /app /app # Add initialization script COPY --chmod=0755 scripts/init.sh /usr/local/bin/init.sh -# Prepare app directory -RUN rm -rf ${APP_DIR}/* \ - && chown ${USER}: ${APP_DIR} - -# Create directory for artisan tinker (init.sh) -RUN mkdir /config/psysh \ - && chown ${USER}: /config/psysh - -# Change owner for caddy directories -RUN chown -R ${USER}: \ - /data/caddy \ - /config/caddy - -USER ${USER} - -# Setup InvoiceNinja -RUN curl -s "https://api.github.com/repos/invoiceninja/invoiceninja/releases/latest" | \ - grep -o '"browser_download_url": "[^"]*invoiceninja.tar"' | \ - cut -d '"' -f 4 | \ - xargs curl -sL | \ - tar -oxz -C ${APP_DIR} \ - && ln -s ${APP_DIR}/resources/views/react/index.blade.php ${APP_DIR}/public/index.html \ - # Set permissions: directories 755, files 644 - && chmod -R a=r,u+w,a+X ${APP_DIR} \ - # Install dependencies - && composer install --working-dir=${APP_DIR} --no-dev --no-scripts --no-autoloader \ - && composer dump-autoload --working-dir=${APP_DIR} --optimize \ - && frankenphp php-cli ${APP_DIR}/artisan storage:link - -ENTRYPOINT ["/usr/local/bin/init.sh"] - -CMD ["frankenphp", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"] +USER ${user} diff --git a/debian/docker-compose.yml b/debian/docker-compose.yml index dbe8935..2d14dc6 100644 --- a/debian/docker-compose.yml +++ b/debian/docker-compose.yml @@ -1,3 +1,5 @@ +# name: invoiceninja + x-logging: &default-logging options: max-size: "10m" @@ -17,14 +19,14 @@ services: context: . image: invoiceninja/invoiceninja-debian:${TAG:-latest} restart: unless-stopped + # php artisan help octane:frankenphp + command: --log-level=info ports: - - "80:80" + - "80:8000" env_file: - ./.env environment: LARAVEL_ROLE: app - # https://frankenphp.dev/docs/production/#preparing-your-app - SERVER_NAME: :80 <<: *volumes # HEALTHCHECK from frankenphp image healthcheck: @@ -43,6 +45,8 @@ services: app-worker: image: invoiceninja/invoiceninja-debian:${TAG:-latest} restart: unless-stopped + # php artisan help queue:work + command: --verbose --sleep=3 --tries=3 --max-time=3600 deploy: mode: replicated replicas: 2 @@ -59,6 +63,8 @@ services: app-scheduler: image: invoiceninja/invoiceninja-debian:${TAG:-latest} restart: unless-stopped + # php artisan help schedule:work + command: --verbose env_file: - ./.env environment: diff --git a/debian/scripts/init.sh b/debian/scripts/init.sh index b432b52..bb6ab65 100755 --- a/debian/scripts/init.sh +++ b/debian/scripts/init.sh @@ -1,42 +1,57 @@ #!/bin/sh -eu -app_dir=${APP_DIR:-/app} +# Fallback to app role=${LARAVEL_ROLE:-app} -if [ "$*" = 'frankenphp run --config /etc/caddy/Caddyfile --adapter caddyfile' ]; then +# Check for default CMD, flag(s) or empty CMD +if [ "$*" = 'frankenphp php-cli artisan octane:frankenphp' ] || [ "${1#-}" != "$1" ] || [ "$#" -eq "0" ]; then + # Run app if [ "${role}" = "app" ]; then + cmd="frankenphp php-cli artisan octane:frankenphp" + if [ "$APP_ENV" = "production" ]; then - frankenphp php-cli "${app_dir}"/artisan optimize + frankenphp php-cli artisan optimize fi - frankenphp php-cli "${app_dir}"/artisan package:discover + frankenphp php-cli artisan package:discover - frankenphp php-cli "${app_dir}"/artisan migrate --force + # Run migrations (if any) + frankenphp php-cli artisan migrate --force # If first IN run, it needs to be initialized - if [ "$(frankenphp php-cli "${app_dir}"/artisan tinker --execute='echo Schema::hasTable("accounts") && !App\Models\Account::all()->first();')" = "1" ]; then + if [ "$(frankenphp php-cli artisan tinker --execute='echo Schema::hasTable("accounts") && !App\Models\Account::all()->first();')" = "1" ]; then echo "Running initialization..." - frankenphp php-cli "${app_dir}"/artisan db:seed --force + frankenphp php-cli artisan db:seed --force if [ -n "${IN_USER_EMAIL}" ] && [ -n "${IN_PASSWORD}" ]; then - frankenphp php-cli "${app_dir}"/artisan ninja:create-account --email "${IN_USER_EMAIL}" --password "${IN_PASSWORD}" + frankenphp php-cli artisan ninja:create-account --email "${IN_USER_EMAIL}" --password "${IN_PASSWORD}" else echo "Initialization failed - Set IN_USER_EMAIL and IN_PASSWORD in .env" exit 1 fi fi + echo "Production setup completed" + # Run worker elif [ "${role}" = "worker" ]; then - exec frankenphp php-cli "${app_dir}"/artisan queue:work -v --sleep=3 --tries=3 --max-time=3600 + cmd="frankenphp php-cli artisan queue:work" + # Run scheduler elif [ "${role}" = "scheduler" ]; then - exec frankenphp php-cli "${app_dir}"/artisan schedule:work -v + cmd="frankenphp php-cli artisan schedule:work" + # Invalid role else echo "Invalid role: ${role}" exit 1 fi + # Append flag(s) to role cmd + if [ "${1#-}" != "$1" ]; then + set -- ${cmd} "$@" + else + set -- ${cmd} + fi fi exec "$@"