diff --git a/.idea/docker-container.iml b/.idea/docker-container.iml new file mode 100644 index 0000000..c956989 --- /dev/null +++ b/.idea/docker-container.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/invoiceninja/.env b/invoiceninja/.env new file mode 100644 index 0000000..090c9fc --- /dev/null +++ b/invoiceninja/.env @@ -0,0 +1,62 @@ +# IN application vars +APP_URL=http://invoiceninja.domr.ovh +APP_KEY=base64:RR++yx2rJ9kdxbdh3+AmbHLDQu+Q76i++co9Y8ybbno= +APP_ENV=production +APP_DEBUG=true +REQUIRE_HTTPS=false +PHANTOMJS_PDF_GENERATION=false +PDF_GENERATOR=snappdf +TRUSTED_PROXIES='*' + + +CACHE_DRIVER=redis +QUEUE_CONNECTION=redis +SESSION_DRIVER=redis + +REDIS_HOST=redis +REDIS_PASSWORD=null +REDIS_PORT=6379 + +FILESYSTEM_DISK=debian_docker + +# DB connection +DB_HOST=mysql +DB_PORT=3306 +DB_DATABASE=ninja +DB_USERNAME=ninja +DB_PASSWORD=ninja +DB_ROOT_PASSWORD=80DKugiSxJb5Sr +DB_CONNECTION=mysql + +# Create initial user +# Default to these values if empty +IN_USER_EMAIL=admin@domroese.eu +IN_PASSWORD=zLI6XKwwXiWhSu +# IN_USER_EMAIL= +# IN_PASSWORD= + +# Mail options +MAIL_MAILER=log +MAIL_HOST=smtp.mailtrap.io +MAIL_PORT=2525 +MAIL_USERNAME=null +MAIL_PASSWORD=null +MAIL_ENCRYPTION=null +MAIL_FROM_ADDRESS='soenke@domroese.eu' +MAIL_FROM_NAME='Sönke Domröse - InvoiceNinja' + +# MySQL +MYSQL_ROOT_PASSWORD=80DKugiSxJb5Sr +MYSQL_USER=ninja +MYSQL_PASSWORD=ninja +MYSQL_DATABASE=ninja + +# GoCardless/Nordigen API key for banking integration +NORDIGEN_SECRET_ID= +NORDIGEN_SECRET_KEY= + +IS_DOCKER=true +SCOUT_DRIVER=null +#SNAPPDF_CHROMIUM_PATH=/usr/bin/google-chrome-stable + + diff --git a/invoiceninja/Caddyfilepart b/invoiceninja/Caddyfilepart new file mode 100644 index 0000000..93289cf --- /dev/null +++ b/invoiceninja/Caddyfilepart @@ -0,0 +1,5 @@ +invoiceninja.domr.ovh, +invoiceninja.home.domroese.eu { + tls soenke@domroese.eu + reverse_proxy 192.168.1.65:8012 +} diff --git a/invoiceninja/Dockerfile b/invoiceninja/Dockerfile new file mode 100644 index 0000000..b742c8b --- /dev/null +++ b/invoiceninja/Dockerfile @@ -0,0 +1,91 @@ +ARG PHP=8.4 + +FROM php:${PHP}-fpm AS prepare-app + +ARG URL=https://github.com/invoiceninja/invoiceninja/releases/latest/download/invoiceninja.tar.gz + +ADD ${URL} /tmp/invoiceninja.tar.gz + +RUN tar -xzf /tmp/invoiceninja.tar.gz -C /var/www/html \ + && ln -s /var/www/html/resources/views/react/index.blade.php /var/www/html/public/index.html \ + && php artisan storage:link \ + # Workaround for application updates + && mv /var/www/html/public /tmp/public + +# ================== +# InvoiceNinja image +# ================== +FROM php:${PHP}-fpm + +# PHP modules +ARG php_require="bcmath gd mbstring pdo_mysql zip" +ARG php_suggest="exif imagick intl pcntl saxon soap" +ARG php_extra="opcache" + +# Install system dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + libfcgi-bin \ + mariadb-client \ + gpg \ + rsync \ + supervisor \ + # Unicode support for PDF + fonts-noto-cjk-extra \ + fonts-wqy-microhei \ + fonts-wqy-zenhei \ + 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; \ + elif [ "$(dpkg --print-architecture)" = "arm64" ]; then \ + apt-get install -y --no-install-recommends \ + chromium; \ + fi \ + # Create config directory for chromium/google-chrome-stable + && mkdir /var/www/.config \ + && chown www-data:www-data /var/www/.config \ + # Cleanup + && apt-get purge -y gpg \ + && apt-get autoremove -y \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Install PHP extensions +COPY --from=ghcr.io/mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/ + +RUN install-php-extensions \ + ${php_require} \ + ${php_suggest} \ + ${php_extra} + +# Configure PHP +RUN ln -s "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" + +COPY php/php.ini /usr/local/etc/php/conf.d/invoiceninja.ini + +COPY php/php-fpm.conf /usr/local/etc/php-fpm.d/invoiceninja.conf + +# Workaround: Disable SSL for mariadb-client for compatibility with MySQL +RUN echo "skip-ssl = true" >> /etc/mysql/mariadb.conf.d/50-client.cnf + +# Setup supervisor +COPY supervisor/supervisord.conf /etc/supervisor/conf.d/supervisord.conf + +# Setup InvoiceNinja +COPY --from=prepare-app --chown=www-data:www-data /var/www/html /var/www/html +COPY --from=prepare-app --chown=www-data:www-data /tmp/public /tmp/public + +# Add initialization script +COPY --chmod=0755 scripts/init.sh /usr/local/bin/init.sh + +# Health check +HEALTHCHECK --start-period=100s \ + CMD REMOTE_ADDR=127.0.0.1 REQUEST_URI=/health REQUEST_METHOD=GET SCRIPT_FILENAME=/var/www/html/public/index.php cgi-fcgi -bind -connect 127.0.0.1:9000 | grep '{"status":"ok","message":"API is healthy"}' + +ENTRYPOINT ["/usr/local/bin/init.sh"] +CMD ["supervisord", "-c", "/etc/supervisor/supervisord.conf"] diff --git a/invoiceninja/docker-compose.yml b/invoiceninja/docker-compose.yml new file mode 100644 index 0000000..ea3ba3b --- /dev/null +++ b/invoiceninja/docker-compose.yml @@ -0,0 +1,63 @@ +services: + app: + build: + context: debian + image: invoiceninja/invoiceninja-debian:${TAG:-latest} + restart: unless-stopped + env_file: + - .env + volumes: + # - ./php/php.ini:/usr/local/etc/php/conf.d/invoiceninja.ini:ro + # - ./php/php-fpm.conf:/usr/local/etc/php-fpm.d/invoiceninja.conf:ro + # - ./supervisor/supervisord.conf:/etc/supervisor/conf.d/supervisord.conf:ro + - /home/soenke/docker-data/invoiceninja/app_public:/var/www/html/public + - /home/soenke/docker-data/invoiceninja/app_storage:/var/www/html/storage + depends_on: + mysql: + condition: service_healthy + redis: + condition: service_healthy + + nginx: + image: nginx:alpine + restart: unless-stopped + ports: + - "8012:80" + volumes: + - /home/soenke/docker-data/invoiceninja/nginx:/etc/nginx/conf.d:ro + - /home/soenke/docker-data/invoiceninja/app_public:/var/www/html/public:ro + - /home/soenke/docker-data/invoiceninja/app_storage:/var/www/html/storage:ro + depends_on: + app: + condition: service_healthy + + mysql: + image: mysql:8 + restart: unless-stopped + environment: + MYSQL_DATABASE: ${DB_DATABASE} + MYSQL_USER: ${DB_USERNAME} + MYSQL_PASSWORD: ${DB_PASSWORD} + MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} + volumes: + - /home/soenke/docker-data/invoiceninja/mysql_data:/var/lib/mysql + healthcheck: + test: + [ + "CMD", + "mysqladmin", + "ping", + "-h", + "localhost", + "-u${MYSQL_USER}", + "-p${MYSQL_PASSWORD}", + ] + + redis: + image: redis:alpine + restart: unless-stopped + volumes: + - /home/soenke/docker-data/invoiceninja/redis_data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + diff --git a/invoiceninja/nginx/invoiceninja.conf b/invoiceninja/nginx/invoiceninja.conf new file mode 100644 index 0000000..78add9d --- /dev/null +++ b/invoiceninja/nginx/invoiceninja.conf @@ -0,0 +1,14 @@ +# https://nginx.org/en/docs/http/ngx_http_core_module.html +client_max_body_size 10M; +client_body_buffer_size 10M; +server_tokens off; + +# https://nginx.org/en/docs/http/ngx_http_fastcgi_module.html +fastcgi_buffers 32 16K; + +# https://nginx.org/en/docs/http/ngx_http_gzip_module.html +gzip on; +gzip_comp_level 2; +gzip_min_length 1M; +gzip_proxied any; +gzip_types *; diff --git a/invoiceninja/nginx/laravel.conf b/invoiceninja/nginx/laravel.conf new file mode 100644 index 0000000..aa02988 --- /dev/null +++ b/invoiceninja/nginx/laravel.conf @@ -0,0 +1,32 @@ +# https://laravel.com/docs/master/deployment#nginx +server { + listen 80 default_server; + server_name _; + root /var/www/html/public; + + add_header X-Frame-Options "SAMEORIGIN"; + add_header X-Content-Type-Options "nosniff"; + + index index.php; + + charset utf-8; + + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + location = /favicon.ico { access_log off; log_not_found off; } + location = /robots.txt { access_log off; log_not_found off; } + + error_page 404 /index.php; + + location ~ \.php$ { + fastcgi_pass app:9000; + fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; + include fastcgi_params; + } + + location ~ /\.(?!well-known).* { + deny all; + } +} diff --git a/invoiceninja/php/php-fpm.conf b/invoiceninja/php/php-fpm.conf new file mode 100644 index 0000000..63bbada --- /dev/null +++ b/invoiceninja/php/php-fpm.conf @@ -0,0 +1 @@ +pm.max_children = 10 diff --git a/invoiceninja/php/php.ini b/invoiceninja/php/php.ini new file mode 100644 index 0000000..eb77682 --- /dev/null +++ b/invoiceninja/php/php.ini @@ -0,0 +1,24 @@ +[core] +; https://www.php.net/manual/en/ini.core.php +post_max_size=10M +upload_max_filesize=10M +memory_limit=512M + +[opcache] +; https://www.php.net/manual/en/opcache.installation.php#opcache.installation.recommended +opcache.enable_cli=1 + +[jit] +; https://wiki.php.net/rfc/jit_config_defaults +opcache.jit=tracing +opcache.jit_buffer_size=64M + +[extra] +; http://symfony.com/doc/current/performance.html +opcache.memory_consumption=256 +opcache.max_accelerated_files=20000 +opcache.preload=/var/www/html/preload.php +opcache.preload_user=www-data +opcache.validate_timestamps=0 +realpath_cache_size = 4096K +realpath_cache_ttl = 600 diff --git a/invoiceninja/scripts/init.sh b/invoiceninja/scripts/init.sh new file mode 100644 index 0000000..525bfe1 --- /dev/null +++ b/invoiceninja/scripts/init.sh @@ -0,0 +1,74 @@ +#!/bin/sh -eu + +# Set PDF generation browser path based on architecture +if [ "$(dpkg --print-architecture)" = "amd64" ]; then + export SNAPPDF_CHROMIUM_PATH=/usr/bin/google-chrome-stable +elif [ "$(dpkg --print-architecture)" = "arm64" ]; then + export SNAPPDF_CHROMIUM_PATH=/usr/bin/chromium +fi + +if [ "$*" = 'supervisord -c /etc/supervisor/supervisord.conf' ]; then + + # Check for required folders and create if needed + [ -d /var/www/html/public ] || mkdir -p /var/www/html/public + [ -d /var/www/html/storage/app/public ] || mkdir -p /var/www/html/storage/app/public + [ -d /var/www/html/storage/framework/sessions ] || mkdir -p /var/www/html/storage/framework/sessions + [ -d /var/www/html/storage/framework/views ] || mkdir -p /var/www/html/storage/framework/views + [ -d /var/www/html/storage/framework/cache ] || mkdir -p /var/www/html/storage/framework/cache + + # Workaround for application updates + if [ "$(ls -A /tmp/public)" ]; then + echo "Updating public folder..." + rm -rf /var/www/html/public/.htaccess \ + /var/www/html/public/.well-known \ + /var/www/html/public/* + cp -r /tmp/public/* \ + /tmp/public/.htaccess \ + /tmp/public/.well-known \ + /var/www/html/public/ && + rm -rf /tmp/public/.htaccess \ + /tmp/public/.well-known \ + /tmp/public/* + + fi + echo "Public Folder is up to date" + + # Ensure owner, file and directory permissions are correct + chown -R www-data:www-data \ + /var/www/html/public \ + /var/www/html/storage + find /var/www/html/public \ + /var/www/html/storage \ + -type f -exec chmod 644 {} \; + find /var/www/html/public \ + /var/www/html/storage \ + -type d -exec chmod 755 {} \; + + # Clear and cache config in production + if [ "$APP_ENV" = "production" ]; then + runuser -u www-data -- php artisan migrate --force + runuser -u www-data -- php artisan cache:clear # Clear after the migration + runuser -u www-data -- php artisan ninja:design-update + runuser -u www-data -- php artisan optimize + + # If first IN run, it needs to be initialized + if [ "$(runuser -u www-data -- php artisan tinker --execute='echo Schema::hasTable("accounts") && !App\Models\Account::all()->first();')" = "1" ]; then + echo "Running initialization..." + + runuser -u www-data -- php artisan db:seed --force + + if [ -n "${IN_USER_EMAIL}" ] && [ -n "${IN_PASSWORD}" ]; then + runuser -u www-data -- php 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" + fi + + echo "Starting supervisord..." +fi + +exec "$@" \ No newline at end of file diff --git a/invoiceninja/supervisor/supervisord.conf b/invoiceninja/supervisor/supervisord.conf new file mode 100644 index 0000000..430caed --- /dev/null +++ b/invoiceninja/supervisor/supervisord.conf @@ -0,0 +1,39 @@ +[supervisord] +nodaemon=true +user=root +logfile=/dev/null +logfile_maxbytes=0 +pidfile=/var/run/supervisord.pid + +[program:php-fpm] +command=/usr/local/sbin/php-fpm -F +autostart=true +autorestart=true +priority=5 +stdout_logfile=/dev/fd/1 +stdout_logfile_maxbytes=0 +redirect_stderr=true + +[program:queue-worker] +process_name=%(program_name)s_%(process_num)02d +command=php /var/www/html/artisan queue:work --sleep=3 --tries=3 --max-time=3600 --verbose +autostart=true +autorestart=true +stopasgroup=true +killasgroup=true +user=www-data +numprocs=2 +environment=HOME="/var/www" +stdout_logfile=/dev/fd/1 +stdout_logfile_maxbytes=0 +redirect_stderr=true +stopwaitsecs=3600 + +[program:scheduler] +command=php /var/www/html/artisan schedule:work --verbose +autostart=true +autorestart=true +user=www-data +stdout_logfile=/dev/fd/1 +stdout_logfile_maxbytes=0 +redirect_stderr=true