Work on Invoice Ninja v5.10.29
* Module now uses caddy instead of nginx * Module now has adminEmail and adminPassword options
This commit is contained in:
parent
e8461e398c
commit
60640a5023
10
Makefile
10
Makefile
@ -11,8 +11,7 @@
|
||||
|
||||
# Since all targets are phony, all targets should be listed here
|
||||
# One target per line
|
||||
.PHONY: boot-graphical-vm \
|
||||
boot-vm \
|
||||
.PHONY: boot-vm \
|
||||
build-vm \
|
||||
clean \
|
||||
format-nix-files \
|
||||
@ -29,8 +28,8 @@ format-nix-files: ## Format nix files using the nixpkgs-fmt tool
|
||||
find . -name "*.nix" -exec nixpkgs-fmt {} \;
|
||||
|
||||
clean: ## Clean build artifacts and shutdown running virtual machines
|
||||
rm result
|
||||
rm nixos.qcow2
|
||||
rm result > /dev/null 2>&1
|
||||
rm nixos.qcow2 > /dev/null 2>&1
|
||||
pkill qemu
|
||||
exit 0
|
||||
|
||||
@ -46,6 +45,3 @@ boot-vm: ## Run virtual machine in current terminal
|
||||
-nographic; \
|
||||
reset
|
||||
|
||||
boot-graphical-vm: ## Run virtual machine in QEMU window
|
||||
./result/bin/run-nixos-vm
|
||||
|
||||
|
||||
13
config-filesystems.patch
Normal file
13
config-filesystems.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/config/filesystems.php b/config/filesystems.php
|
||||
index a104af7a81..a4c87ba3ff 100644
|
||||
--- a/config/filesystems.php
|
||||
+++ b/config/filesystems.php
|
||||
@@ -37,7 +37,7 @@ return [
|
||||
'root' => base_path(),
|
||||
'permissions' => [
|
||||
'file' => [
|
||||
- 'public' => 0664,
|
||||
+ 'public' => 0444,
|
||||
'private' => 0600,
|
||||
],
|
||||
'dir' => [
|
||||
26
default.nix
26
default.nix
@ -8,25 +8,23 @@
|
||||
}:
|
||||
|
||||
let
|
||||
# Helper script to generate an APP_KEY for .env
|
||||
generate-invoice-ninja-app-key = writers.writeBashBin "generate-laravel-key" ''
|
||||
echo "APP_KEY=base64:$(${openssl}/bin/openssl rand -base64 32)"
|
||||
'';
|
||||
configFilesystemsPatch = "./config-filesystems.patch";
|
||||
in
|
||||
php.buildComposerProject (finalAttrs: {
|
||||
pname = "invoice-ninja";
|
||||
version = "5.10.3";
|
||||
version = "5.10.29";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "invoiceninja";
|
||||
repo = "invoiceninja";
|
||||
rev = "v${finalAttrs.version}";
|
||||
hash = "sha256-QL6L+yT1yRQUTTGYGjaC4zbvzgw4ozgJSP2bYJCf014=";
|
||||
hash = "sha256-nhLt3DXW0q07ZhDq23mHwbVmqHZor+p925/yrKXum54=";
|
||||
};
|
||||
|
||||
vendorHash = "sha256-LGNBgaWWX2a8w9uE3+fVtBDqgbcv69FNnka4HjZKqsQ=";
|
||||
vendorHash = "sha256-NVvx1aKhbC5XuXt2+gS2c3ulNWoCKrYNnEleBuAcftQ=";
|
||||
|
||||
propagatedBuildInput = [ generate-invoice-ninja-app-key ];
|
||||
# Patch sources to allow more restrictive permissions
|
||||
patches = [ ./config-filesystems.patch ];
|
||||
|
||||
# Upstream composer.json has invalid license, webpatser/laravel-countries package is pointing
|
||||
# to commit-ref, and php required in require and require-dev
|
||||
@ -36,21 +34,17 @@ php.buildComposerProject (finalAttrs: {
|
||||
mv "$out/share/php/${finalAttrs.pname}"/* $out
|
||||
rm -R $out/bootstrap/cache
|
||||
|
||||
rm -rf $out/public/storage
|
||||
|
||||
# Move static contents for the NixOS module to pick it up, if needed.
|
||||
mv $out/bootstrap $out/bootstrap-static
|
||||
mv $out/storage $out/storage-static
|
||||
mv $out/vite.config.ts $out/vite.config.ts.static
|
||||
mv $out/public/images $out/public/images-static
|
||||
mv $out/public/react $out/public/react-static
|
||||
|
||||
ln -s ${dataDir}/public/images $out/public/
|
||||
ln -s ${dataDir}/public/react $out/public/
|
||||
ln -s ${dataDir}/vite.config.ts $out/
|
||||
# Link NixOS module files to derivation output
|
||||
ln -s ${dataDir}/.env $out/.env
|
||||
ln -s ${dataDir}/storage $out/
|
||||
ln -s ${runtimeDir} $out/bootstrap
|
||||
|
||||
chmod +x $out/artisan
|
||||
ln -s ${dataDir}/storage/app/public $out/public/storage
|
||||
'';
|
||||
|
||||
meta = {
|
||||
|
||||
@ -11,7 +11,7 @@ let
|
||||
# PHP environment
|
||||
phpPackage = cfg.phpPackage.buildEnv {
|
||||
extensions = { enabled, all }: enabled ++ (with all;
|
||||
[ bcmath ctype curl fileinfo gd gmp iconv mbstring mysqli openssl pdo tokenizer zip ]
|
||||
[ bcmath ctype curl fileinfo gd gmp iconv imagick mbstring mysqli openssl pdo tokenizer zip ]
|
||||
);
|
||||
|
||||
extraConfig = "memory_limit = 1024M";
|
||||
@ -141,6 +141,18 @@ in
|
||||
'';
|
||||
};
|
||||
|
||||
adminEmail = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "example@email.com";
|
||||
description = "Email address of the first (admin) account for this Invoice Ninja installation";
|
||||
};
|
||||
|
||||
adminPassword = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "example";
|
||||
description = "Password of the first (admin) account for this Invoice Ninja installation";
|
||||
};
|
||||
|
||||
database = {
|
||||
createLocally = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
@ -155,31 +167,12 @@ in
|
||||
};
|
||||
};
|
||||
|
||||
enableACME = lib.mkOption {
|
||||
caddy = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether an ACME certificate should be used to secure connections to the server.
|
||||
'';
|
||||
};
|
||||
|
||||
nginx = lib.mkOption {
|
||||
type = lib.types.nullOr (lib.types.submodule
|
||||
(import <nixpkgs/nixos/modules/services/web-servers/nginx/vhost-options.nix> {
|
||||
inherit config lib;
|
||||
}));
|
||||
default = null;
|
||||
example = ''
|
||||
{
|
||||
enableACME = true;
|
||||
forceHttps = true;
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
With this option, you can customize an nginx virtual host which already has sensible defaults
|
||||
for Invoice Ninja. Set to {} if you do not need any customization to the virtual host.
|
||||
If enabled, then by default, the {option}`serverName` is `''${domain}`. If this is set to
|
||||
null (the default), no nginx virtualHost will be configured.
|
||||
Whether to enable a preconfigured Caddy server to serve
|
||||
Invoice Ninja.
|
||||
'';
|
||||
};
|
||||
};
|
||||
@ -225,6 +218,7 @@ in
|
||||
NINJA_ENVIRONMENT = lib.mkDefault "selfhost";
|
||||
PDF_GENERATOR = lib.mkDefault "snappdf";
|
||||
SNAPPDF_CHROMIUM_PATH = lib.mkDefault "${chromium}/bin/chromium";
|
||||
PRECONFIGURED_INSTALL = lib.mkDefault true;
|
||||
})
|
||||
(lib.mkIf (cfg.database.createLocally) {
|
||||
DB_CONNECTION = lib.mkDefault "mysql";
|
||||
@ -235,79 +229,31 @@ in
|
||||
})
|
||||
];
|
||||
|
||||
services.phpfpm.pools.invoiceninja = {
|
||||
services.phpfpm.pools.invoice-ninja = {
|
||||
inherit user group phpPackage;
|
||||
inherit (cfg.phpfpm) settings;
|
||||
};
|
||||
|
||||
users.users."${config.services.nginx.user}" = lib.mkIf (cfg.nginx != null) { extraGroups = [ cfg.group ]; };
|
||||
services.nginx = lib.mkIf (cfg.nginx != null) {
|
||||
users.users."${config.services.caddy.user}" = lib.mkIf (cfg.caddy != false) { extraGroups = [ cfg.group ]; };
|
||||
services.caddy =
|
||||
let
|
||||
caddyDomain = if (cfg.domain == "localhost") then ":80" else cfg.domain;
|
||||
in
|
||||
lib.mkIf (cfg.caddy != false) {
|
||||
enable = true;
|
||||
|
||||
clientMaxBodySize = "20m";
|
||||
|
||||
recommendedGzipSettings = true;
|
||||
recommendedOptimisation = true;
|
||||
recommendedProxySettings = true;
|
||||
recommendedTlsSettings = true;
|
||||
|
||||
appendHttpConfig = ''
|
||||
# Add HSTS header with preloading to HTTPS requests.
|
||||
# Adding this header to HTTP requests is discouraged
|
||||
map $scheme $hsts_header {
|
||||
https "max-age=31536000; includeSubdomains; preload";
|
||||
}
|
||||
add_header Strict-Transport-Security $hsts_header;
|
||||
|
||||
# Minimize information leaked to other domains
|
||||
add_header 'Referrer-Policy' 'origin-when-cross-origin';
|
||||
|
||||
# Disable embedding as a frame
|
||||
add_header X-Frame-Options DENY;
|
||||
|
||||
# Prevent injection of code in other mime types (XSS Attacks)
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
|
||||
# This might create errors
|
||||
proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";
|
||||
globalConfig = lib.mkIf (cfg.domain == "localhost") ''
|
||||
auto_https disable_redirects
|
||||
'';
|
||||
|
||||
virtualHosts."${cfg.domain}" = lib.mkMerge [
|
||||
cfg.nginx
|
||||
{
|
||||
inherit (cfg) enableACME;
|
||||
forceSSL = lib.mkDefault (if cfg.enableACME then true else false);
|
||||
root = lib.mkForce "${invoice-ninja}/public/";
|
||||
locations."= /index.php" = {
|
||||
extraConfig = ''
|
||||
fastcgi_pass unix:${config.services.phpfpm.pools.invoiceninja.socket};
|
||||
fastcgi_index index.php;
|
||||
virtualHosts."${caddyDomain}".extraConfig = ''
|
||||
encode zstd gzip
|
||||
root * ${invoice-ninja}/public/
|
||||
php_fastcgi unix/${config.services.phpfpm.pools.invoice-ninja.socket}
|
||||
try_files {path} {path}/ /public/{path} /index.php?{query}
|
||||
file_server
|
||||
'';
|
||||
};
|
||||
locations."~ \\.php$" = {
|
||||
return = 403;
|
||||
};
|
||||
locations."/" = {
|
||||
tryFiles = "$uri $uri/ /index.php?$query_string";
|
||||
extraConfig = ''
|
||||
# Add your rewrite rule for non-existent files
|
||||
if (!-e $request_filename) {
|
||||
rewrite ^(.+)$ /index.php?q=$1 last;
|
||||
}
|
||||
'';
|
||||
};
|
||||
locations."~ /\\.ht" = {
|
||||
extraConfig = ''
|
||||
deny all;
|
||||
'';
|
||||
};
|
||||
extraConfig = ''
|
||||
index index.php index.html index.htm;
|
||||
error_page 404 /index.php;
|
||||
'';
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
services.mysql = lib.mkIf (cfg.database.createLocally) {
|
||||
enable = lib.mkDefault true;
|
||||
@ -354,7 +300,7 @@ in
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = lib.optional cfg.database.createLocally "mysql.service";
|
||||
requires = lib.optional cfg.database.createLocally "mysql.service";
|
||||
path = with pkgs; [ bash invoice-ninja-manage rsync ] ++ extraPrograms;
|
||||
path = with pkgs; [ bash invoice-ninja-manage rsync config.services.mysql.package ] ++ extraPrograms;
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
@ -376,37 +322,30 @@ in
|
||||
# Concatenate non-secret .env and secret .env
|
||||
rm -f ${cfg.dataDir}/.env
|
||||
cp --no-preserve=all ${configFile} ${cfg.dataDir}/.env
|
||||
# echo -e '\n' >> ${cfg.dataDir}/.env
|
||||
echo -e '\n' >> ${cfg.dataDir}/.env
|
||||
cat "$CREDENTIALS_DIRECTORY/env-secrets" >> ${cfg.dataDir}/.env
|
||||
|
||||
# Link the static storage (package provided) to the runtime storage
|
||||
# Necessary for cities.json and static images.
|
||||
mkdir -p ${cfg.dataDir}/storage
|
||||
rsync -av --no-perms ${invoice-ninja}/storage-static/ ${cfg.dataDir}/storage
|
||||
chmod -R +w ${cfg.dataDir}/storage
|
||||
|
||||
chmod g+x ${cfg.dataDir}/storage ${cfg.dataDir}/storage/app
|
||||
chmod -R g+rX ${cfg.dataDir}/storage/app/public
|
||||
|
||||
# Link the app.php in the runtime folder.
|
||||
# We cannot link the cache folder only because bootstrap folder needs to be writeable.
|
||||
ln -sf ${invoice-ninja}/bootstrap-static/app.php ${cfg.runtimeDir}/app.php
|
||||
|
||||
# Link the static public/images (package provided) to the runtime public/public/images
|
||||
mkdir -p ${cfg.dataDir}/public/images
|
||||
rsync -av --no-perms ${invoice-ninja}/public/images-static/ ${cfg.dataDir}/public/images
|
||||
# Perform the first migration
|
||||
[[ ! -f ${cfg.dataDir}/.initial-migration ]] && invoice-ninja-manage migrate --force && touch ${cfg.dataDir}/.initial-migration
|
||||
|
||||
# Link the static public/react (package provided) to the runtime public/public/react
|
||||
mkdir -p ${cfg.dataDir}/public/react
|
||||
rsync -av --no-perms ${invoice-ninja}/public/react-static/ ${cfg.dataDir}/public/react
|
||||
# Create Invoice Ninja admin account
|
||||
[[ ! -f ${cfg.dataDir}/.admin-created ]] \
|
||||
&& invoice-ninja-manage ninja:create-account --email=${cfg.adminEmail} --password=${cfg.adminPassword} \
|
||||
&& touch ${cfg.dataDir}/.admin-created
|
||||
|
||||
chmod -R +w ${cfg.dataDir}/public
|
||||
chmod -R g+rwX ${cfg.dataDir}/public
|
||||
|
||||
# Link the static vite.config.ts (package provided to the runtime vite.config.ts
|
||||
rsync -av --no-perms ${invoice-ninja}/vite.config.ts.static ${cfg.dataDir}/vite.config.ts
|
||||
chmod +w ${cfg.dataDir}/vite.config.ts
|
||||
chmod g+rw ${cfg.dataDir}/vite.config.ts
|
||||
# Recent releases make the React interface default
|
||||
# Currently this is broken so we switch back to the Flutter interface
|
||||
[[ ! -f ${cfg.dataDir}/.react-disabled ]] \
|
||||
&& mysql -D ${cfg.database.name} -e 'UPDATE accounts SET set_react_as_default_ap = 0;' \
|
||||
&& touch ${cfg.dataDir}/.react-disabled
|
||||
|
||||
invoice-ninja-manage route:cache
|
||||
invoice-ninja-manage view:cache
|
||||
@ -414,11 +353,32 @@ in
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
# Cache must live across multiple systemd units runtimes.
|
||||
"d ${cfg.runtimeDir}/ 0700 ${user} ${group} - -"
|
||||
"d ${cfg.runtimeDir}/cache 0700 ${user} ${group} - -"
|
||||
];
|
||||
systemd.tmpfiles.settings."10-invoice-ninja" = lib.attrsets.genAttrs [
|
||||
cfg.dataDir
|
||||
"${cfg.dataDir}/storage"
|
||||
"${cfg.dataDir}/storage/app"
|
||||
"${cfg.dataDir}/storage/app/public"
|
||||
"${cfg.dataDir}/storage/framework"
|
||||
"${cfg.dataDir}/storage/framework/cache"
|
||||
"${cfg.dataDir}/storage/framework/sessions"
|
||||
"${cfg.dataDir}/storage/framework/views"
|
||||
"${cfg.dataDir}/storage/logs"
|
||||
] (n: {
|
||||
d = {
|
||||
user = user;
|
||||
group = group;
|
||||
mode = "0770";
|
||||
};
|
||||
}) // lib.attrsets.genAttrs [
|
||||
cfg.runtimeDir
|
||||
"${cfg.runtimeDir}/cache"
|
||||
] (n: {
|
||||
d = {
|
||||
user = user;
|
||||
group = group;
|
||||
mode = "0770";
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
services.invoice-ninja = {
|
||||
enable = true;
|
||||
database.createLocally = true;
|
||||
nginx = { };
|
||||
caddy = true;
|
||||
secretFile = ./test-secrets.env;
|
||||
};
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user