Add invoiceninja

This commit is contained in:
2026-04-28 12:32:56 +02:00
parent 614f48a705
commit 485dfb4f5e
11 changed files with 413 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
+62
View File
@@ -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
+5
View File
@@ -0,0 +1,5 @@
invoiceninja.domr.ovh,
invoiceninja.home.domroese.eu {
tls soenke@domroese.eu
reverse_proxy 192.168.1.65:8012
}
+91
View File
@@ -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"]
+63
View File
@@ -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"]
+14
View File
@@ -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 *;
+32
View File
@@ -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;
}
}
+1
View File
@@ -0,0 +1 @@
pm.max_children = 10
+24
View File
@@ -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
+74
View File
@@ -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 "$@"
+39
View File
@@ -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