Compare commits

...

18 Commits

Author SHA1 Message Date
a42d01edd3 package.nix: bumped version to 5.12.13 2025-08-13 18:34:59 -04:00
1df11adbde Merge branch 'master' of gitea.awkawb.cloud:awkawb/invoiceninja-nixos 2025-03-01 11:11:58 -05:00
e8b208accb flake.lock: updated flake inputs 2025-02-24 17:32:32 -05:00
295b299fdd package.nix: bumped version to 5.11.41 2025-02-24 17:29:51 -05:00
d898e26cb1 package.nix: bumped version to 5.11.24 2025-01-14 16:54:51 -05:00
7b2072e5b0 package.nix: remove unneeded move and link statements 2025-01-02 16:55:02 -05:00
611709cb62 nixos-module/invoiceninja.nix: changed nixos module definition for nginx to allow local development, removed unneeded link in data setup service 2025-01-02 16:53:58 -05:00
730715014e tests/default.nix: modified to reflect package name change 2025-01-02 16:22:09 -05:00
4bddba21da flake.nix: fixed attribute name 2025-01-01 16:15:10 -05:00
8549d2f23d Makefile: build-vm now reflects the repo being a flake 2025-01-01 12:19:25 -05:00
91d8d8a908 flake.nix: modified to reflect package name change, add nixos config for development 2025-01-01 12:17:55 -05:00
2b98375f9b tests/default.nix: added stateVersion, added import of nixos module, fixed typo 2025-01-01 12:12:59 -05:00
4cb4a1c05d Package is now 'invoiceninja', modified code to reflect name change, changed nixos module definition for caddy to allow local development 2025-01-01 12:11:18 -05:00
14976fdae9 Added tests directory for testing in vm 2024-12-30 10:19:29 -05:00
1e48f190da Removed unused files and directories 2024-12-28 16:19:55 -05:00
85de24655f packages.nix: bumped version to 5.11.7 2024-12-28 16:11:25 -05:00
9bb2d8f352 package.nix: bumped version to 5.11.6 2024-12-28 16:09:49 -05:00
e043ebbaea package.nix: bumped version to 5.11.5 2024-12-28 15:40:58 -05:00
13 changed files with 167 additions and 247 deletions

View File

@ -1,11 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2024.07.04] 2024-07-04
- Initial release

View File

@ -34,14 +34,12 @@ clean: ## Clean build artifacts and shutdown running virtual machines
exit 0 exit 0
build-vm: clean ## Build virtual machine for testing build-vm: clean ## Build virtual machine for testing
nixos-rebuild build-vm \ nix build ".#nixosConfigurations.test.config.system.build.vm"
-I nixpkgs=http://nixos.org/channels/nixos-24.05/nixexprs.tar.xz \
-I nixos-config=./tests/test-config.nix
boot-vm: ## Run virtual machine in current terminal boot-vm: ## Run virtual machine in current terminal
QEMU_KERNEL_PARAMS=console=ttyS0 \ QEMU_KERNEL_PARAMS=console=ttyS0 \
QEMU_NET_OPTS=hostfwd=tcp::8080-:80 \ QEMU_NET_OPTS=hostfwd=tcp::8080-:80 \
./result/bin/run-nixos-vm \ QEMU_OPTS=-nographic \
-nographic; \ ./result/bin/run-nixos-vm
reset reset

56
flake.lock generated
View File

@ -3,11 +3,11 @@
"flake-compat": { "flake-compat": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1696426674, "lastModified": 1733328505,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
"owner": "edolstra", "owner": "edolstra",
"repo": "flake-compat", "repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -24,11 +24,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1719994518, "lastModified": 1733312601,
"narHash": "sha256-pQMhCCHyQGRzdfAkdJ4cIWiw+JNuWsTX7f0ZYSyz0VY=", "narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "9227223f6d922fee3c7b190b2cc238a99527bbb7", "rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -55,11 +55,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1721042469, "lastModified": 1734279981,
"narHash": "sha256-6FPUl7HVtvRHCCBQne7Ylp4p+dpP3P/OYuzjztZ4s70=", "narHash": "sha256-NdaCraHPp8iYMWzdXAt5Nv6sA3MUzlCiGiR586TCwo0=",
"owner": "cachix", "owner": "cachix",
"repo": "git-hooks.nix", "repo": "git-hooks.nix",
"rev": "f451c19376071a90d8c58ab1a953c6e9840527fd", "rev": "aa9f40c906904ebd83da78e7f328cd8aeaeae785",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -68,39 +68,21 @@
"type": "github" "type": "github"
} }
}, },
"libgit2": {
"flake": false,
"locked": {
"lastModified": 1715853528,
"narHash": "sha256-J2rCxTecyLbbDdsyBWn9w7r3pbKRMkI9E7RvRgAqBdY=",
"owner": "libgit2",
"repo": "libgit2",
"rev": "36f7e21ad757a3dacc58cf7944329da6bc1d6e96",
"type": "github"
},
"original": {
"owner": "libgit2",
"ref": "v1.8.1",
"repo": "libgit2",
"type": "github"
}
},
"nix": { "nix": {
"inputs": { "inputs": {
"flake-compat": "flake-compat", "flake-compat": "flake-compat",
"flake-parts": "flake-parts", "flake-parts": "flake-parts",
"git-hooks-nix": "git-hooks-nix", "git-hooks-nix": "git-hooks-nix",
"libgit2": "libgit2",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"nixpkgs-23-11": "nixpkgs-23-11", "nixpkgs-23-11": "nixpkgs-23-11",
"nixpkgs-regression": "nixpkgs-regression" "nixpkgs-regression": "nixpkgs-regression"
}, },
"locked": { "locked": {
"lastModified": 1735389339, "lastModified": 1740432616,
"narHash": "sha256-JfQXQL0MysQSfvbw7xHto9YbqZ1VQLFgus+c4KYt6xg=", "narHash": "sha256-2yad3RqbLhCeMK01lpSLXmeZR7J74YeBg4yNAqhzLLk=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nix", "repo": "nix",
"rev": "8a3fc27f1b63a08ac983ee46435a56cf49ebaf4a", "rev": "8384e41b7608b5ff50306d3dec6171483cf2d4cd",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -110,16 +92,16 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1723688146, "lastModified": 1734359947,
"narHash": "sha256-sqLwJcHYeWLOeP/XoLwAtYjr01TISlkOfz+NG82pbdg=", "narHash": "sha256-1Noao/H+N8nFB4Beoy8fgwrcOQLVm9o4zKW1ODaqK9E=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "c3d4ac725177c030b1e289015989da2ad9d56af0", "rev": "48d12d5e70ee91fe8481378e540433a7303dbf6a",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "NixOS",
"ref": "nixos-24.05", "ref": "release-24.11",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }
@ -158,11 +140,11 @@
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1735264675, "lastModified": 1740339700,
"narHash": "sha256-MgdXpeX2GuJbtlBrH9EdsUeWl/yXEubyvxM1G+yO4Ak=", "narHash": "sha256-cbrw7EgQhcdFnu6iS3vane53bEagZQy/xyIkDWpCgVE=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "d49da4c08359e3c39c4e27c74ac7ac9b70085966", "rev": "04ef94c4c1582fd485bbfdb8c4a8ba250e359195",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -1,5 +1,5 @@
{ {
description = "An Invoice Ninja package and a module which can be added to a NixOS configuration"; description = "An Invoice Ninja package and module.";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
@ -18,19 +18,20 @@
overlays = overlayList; overlays = overlayList;
} }
); );
in in
rec { rec {
nixosConfigurations.test = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [ ./tests ];
};
# A Nixpkgs overlay that provides a 'Invoice Ninja' package. overlays.default = final: prev: { invoiceninja = final.callPackage ./package.nix { }; };
overlays.default = final: prev: { invoice-ninja = final.callPackage ./package.nix { }; };
packages = forEachSystem (system: { packages = forEachSystem (system: {
invoice-ninja = pkgsBySystem.${system}.invoice-ninja; invoiceninja = pkgsBySystem.${system}.invoiceninja;
default = pkgsBySystem.${system}.invoice-ninja; default = pkgsBySystem.${system}.invoiceninja;
}); });
nixosModules = import ./nixos-module { overlays = overlayList; }; nixosModules = import ./nixos-module { overlays = overlayList; };
}; };
} }

View File

@ -1,7 +1,7 @@
{ overlays }: { overlays }:
{ {
invoice-ninja = import ./invoice-ninja.nix; invoiceninja = import ./invoiceninja.nix;
overlayNixpkgsForThisInstance = overlayNixpkgsForThisInstance =
{ pkgs, ... }: { { pkgs, ... }: {
nixpkgs = { nixpkgs = {

View File

@ -1,21 +1,20 @@
{ { config
config, , lib
lib, , modulesPath
modulesPath, , options
options, , pkgs
pkgs, , ...
...
}: }:
let let
cfg = config.services.invoice-ninja; cfg = config.services.invoiceninja;
user = cfg.user; user = cfg.user;
group = cfg.group; group = cfg.group;
invoice-ninja = pkgs.callPackage ../package.nix { invoiceninja = pkgs.callPackage ../package.nix {
inherit (cfg) dataDir runtimeDir; inherit (cfg) dataDir runtimeDir;
}; };
configFormat = pkgs.formats.keyValue { }; configFormat = pkgs.formats.keyValue { };
configFile = pkgs.writeText "invoice-ninja-env" (lib.generators.toKeyValue { } cfg.settings); configFile = pkgs.writeText "invoiceninja-env" (lib.generators.toKeyValue { } cfg.settings);
# PHP environment # PHP environment
phpPackage = cfg.phpPackage.buildEnv { phpPackage = cfg.phpPackage.buildEnv {
@ -53,8 +52,8 @@ let
extraPrograms = [ chromium ]; extraPrograms = [ chromium ];
# Management script # Management script
invoice-ninja-manage = pkgs.writeShellScriptBin "invoice-ninja-manage" '' invoiceninja-manage = pkgs.writeShellScriptBin "invoiceninja-manage" ''
cd ${invoice-ninja} cd ${invoiceninja}
sudo=exec sudo=exec
if [[ "$USER" != ${user} ]]; then if [[ "$USER" != ${user} ]]; then
sudo='exec /run/wrappers/bin/sudo -u ${user}' sudo='exec /run/wrappers/bin/sudo -u ${user}'
@ -63,10 +62,10 @@ let
''; '';
in in
{ {
options.services.invoice-ninja = { options.services.invoiceninja = {
enable = lib.mkEnableOption "invoice-ninja"; enable = lib.mkEnableOption "invoiceninja";
package = lib.mkPackageOption pkgs "invoice-ninja" { }; package = lib.mkPackageOption pkgs "invoiceninja" { };
phpPackage = lib.mkPackageOption pkgs "php82" { }; phpPackage = lib.mkPackageOption pkgs "php82" { };
@ -94,7 +93,7 @@ in
''; '';
}; };
msmtp.accounts.invoice-ninja = lib.mkOption { msmtp.accounts.invoiceninja = lib.mkOption {
type = lib.types.attrs; type = lib.types.attrs;
default = { }; default = { };
example = { example = {
@ -107,9 +106,9 @@ in
passwordeval = "cat /secrets/password.txt"; passwordeval = "cat /secrets/password.txt";
}; };
description = '' description = ''
Define the msmtp configuration for an invoice-ninja account which Define the msmtp configuration for an invoiceninja account which
will be used by Invoice Ninja to send email message when will be used by Invoice Ninja to send email message when
`config.services.invoice-ninja.settings.MAIL_MAILER` is `sendmail`. `config.services.invoiceninja.settings.MAIL_MAILER` is `sendmail`.
It is advised to use the `passwordeval` setting to read the password It is advised to use the `passwordeval` setting to read the password
from a secret file to avoid having it written in the world-readable from a secret file to avoid having it written in the world-readable
@ -125,18 +124,18 @@ in
dataDir = lib.mkOption { dataDir = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = "/var/lib/invoice-ninja"; default = "/var/lib/invoiceninja";
description = '' description = ''
State directory of the `invoice-ninja` user which holds State directory of the `invoiceninja` user which holds
the application's state and data. the application's state and data.
''; '';
}; };
runtimeDir = lib.mkOption { runtimeDir = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = "/run/invoice-ninja"; default = "/run/invoiceninja";
description = '' description = ''
Rutime directory of the `invoice-ninja` user which holds Rutime directory of the `invoiceninja` user which holds
the application's caches and temporary files. the application's caches and temporary files.
''; '';
}; };
@ -207,7 +206,7 @@ in
options = { options = {
APP_NAME = lib.mkOption { APP_NAME = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = "Invoice Ninja"; default = ''"Invoice Ninja"'';
description = "Your application name - used in client portal title banner"; description = "Your application name - used in client portal title banner";
}; };
APP_DEBUG = lib.mkOption { APP_DEBUG = lib.mkOption {
@ -256,13 +255,13 @@ in
}; };
MAIL_SENDMAIL_PATH = lib.mkOption { MAIL_SENDMAIL_PATH = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = ''"/run/wrappers/bin/sendmail -t -a invoice-ninja"''; default = ''"/run/wrappers/bin/sendmail -t -a invoiceninja"'';
description = '' description = ''
Path to sendmail along with arguments for Invoice Ninja to use when using sendmail Path to sendmail along with arguments for Invoice Ninja to use when using sendmail
as mail transport agent. as mail transport agent.
:: note :: note
The default value will work with the `msmtp.accounts.invoice-ninja` setting. Only The default value will work with the `msmtp.accounts.invoiceninja` setting. Only
change if you know what your doing. change if you know what your doing.
:: ::
''; '';
@ -294,7 +293,7 @@ in
default = default =
if cfg.redis.createLocally then "redis" else (if cfg.database.createLocally then "database" else "file"); if cfg.redis.createLocally then "redis" else (if cfg.database.createLocally then "database" else "file");
defaultText = lib.literalExpression '' defaultText = lib.literalExpression ''
if config.services.invoice-ninja.redis.enable if config.services.invoiceninja.redis.enable
then "redis" then "redis"
else (if config.services.invoie-ninja.database.createLocally then "database" else "file") else (if config.services.invoie-ninja.database.createLocally then "database" else "file")
''; '';
@ -317,7 +316,7 @@ in
default = default =
if cfg.redis.createLocally then "redis" else (if cfg.database.createLocally then "database" else "sync"); if cfg.redis.createLocally then "redis" else (if cfg.database.createLocally then "database" else "sync");
defaultText = lib.literalExpression '' defaultText = lib.literalExpression ''
if config.services.invoice-ninja.redis.enable if config.services.invoiceninja.redis.enable
then "redis" then "redis"
else (if config.services.invoie-ninja.database.createLocally then "database" else "sync") else (if config.services.invoie-ninja.database.createLocally then "database" else "sync")
''; '';
@ -329,7 +328,7 @@ in
"redis" "redis"
]; ];
default = if cfg.redis.createLocally then "redis" else "file"; default = if cfg.redis.createLocally then "redis" else "file";
defaultText = lib.literalExpression ''if config.services.invoice-ninja.redis.enable then "redis" else "file"''; defaultText = lib.literalExpression ''if config.services.invoiceninja.redis.enable then "redis" else "file"'';
description = "Laravel cache driver for Invoice Ninja to use."; description = "Laravel cache driver for Invoice Ninja to use.";
}; };
}; };
@ -337,7 +336,7 @@ in
}; };
adminAccount = { adminAccount = {
createAdmin = lib.mkOption { enable = lib.mkOption {
type = lib.types.bool; type = lib.types.bool;
default = true; default = true;
description = '' description = ''
@ -347,7 +346,7 @@ in
}; };
email = lib.mkOption { email = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = "example@email.com"; default = "admin@email.com";
description = "Email address of the first (admin) account for this Invoice Ninja installation"; description = "Email address of the first (admin) account for this Invoice Ninja installation";
}; };
passwordFile = lib.mkOption { passwordFile = lib.mkOption {
@ -423,14 +422,14 @@ in
users.groups.invoiceninja = lib.mkIf (cfg.group == "invoiceninja") { }; users.groups.invoiceninja = lib.mkIf (cfg.group == "invoiceninja") { };
environment.systemPackages = [ invoice-ninja-manage ] ++ extraPrograms; environment.systemPackages = [ invoiceninja-manage ] ++ extraPrograms;
programs.msmtp = lib.mkIf (cfg.settings.MAIL_MAILER == "sendmail") { programs.msmtp = lib.mkIf (cfg.settings.MAIL_MAILER == "sendmail") {
inherit (cfg.msmtp) accounts; inherit (cfg.msmtp) accounts;
enable = true; enable = true;
}; };
services.invoice-ninja.settings = lib.mkMerge [ services.invoiceninja.settings = lib.mkMerge [
({ ({
APP_URL = lib.mkDefault ( APP_URL = lib.mkDefault (
if (cfg.hostname == "localhost") then ("http://" + cfg.hostname) else ("https://" + cfg.hostname) if (cfg.hostname == "localhost") then ("http://" + cfg.hostname) else ("https://" + cfg.hostname)
@ -452,12 +451,12 @@ in
DB_USERNAME = lib.mkDefault user; DB_USERNAME = lib.mkDefault user;
}) })
(lib.mkIf cfg.redis.createLocally { (lib.mkIf cfg.redis.createLocally {
REDIS_HOST = lib.mkDefault config.services.redis.servers.invoice-ninja.bind; REDIS_HOST = lib.mkDefault config.services.redis.servers.invoiceninja.bind;
REDIS_PORT = lib.mkDefault config.services.redis.servers.invoice-ninja.port; REDIS_PORT = lib.mkDefault config.services.redis.servers.invoiceninja.port;
}) })
]; ];
services.phpfpm.pools.invoice-ninja = { services.phpfpm.pools.invoiceninja = {
inherit user group phpPackage; inherit user group phpPackage;
settings = { settings = {
@ -474,7 +473,7 @@ in
''; '';
}; };
services.redis.servers.invoice-ninja = lib.mkIf cfg.redis.createLocally { services.redis.servers.invoiceninja = lib.mkIf cfg.redis.createLocally {
enable = true; enable = true;
port = 6379; port = 6379;
}; };
@ -485,7 +484,7 @@ in
services.nginx = lib.mkIf (cfg.proxy.server == "nginx") { services.nginx = lib.mkIf (cfg.proxy.server == "nginx") {
enable = true; enable = true;
recommendedTlsSettings = true; recommendedTlsSettings = (if (cfg.hostname == "localhost") then false else true);
recommendedGzipSettings = true; recommendedGzipSettings = true;
recommendedProxySettings = true; recommendedProxySettings = true;
recommendedOptimisation = true; recommendedOptimisation = true;
@ -495,9 +494,9 @@ in
virtualHosts."${cfg.hostname}" = lib.mkMerge [ virtualHosts."${cfg.hostname}" = lib.mkMerge [
cfg.proxy.nginxConfig cfg.proxy.nginxConfig
{ {
root = lib.mkForce "${invoice-ninja}/public"; root = lib.mkForce "${invoiceninja}/public";
addSSL = lib.mkForce true; addSSL = lib.mkForce (if (cfg.hostname == "localhost") then false else true);
enableACME = lib.mkForce true; enableACME = lib.mkForce (if (cfg.hostname == "localhost") then false else true);
locations = { locations = {
"/".tryFiles = "$uri $uri/ /index.php?$query_string"; "/".tryFiles = "$uri $uri/ /index.php?$query_string";
"/".extraConfig = '' "/".extraConfig = ''
@ -514,7 +513,7 @@ in
''; '';
"~ \\.php$".extraConfig = "return 403;"; "~ \\.php$".extraConfig = "return 403;";
"= /index.php".extraConfig = '' "= /index.php".extraConfig = ''
fastcgi_pass unix:${config.services.phpfpm.pools.invoice-ninja.socket}; fastcgi_pass unix:${config.services.phpfpm.pools.invoiceninja.socket};
''; '';
"~ /\\.ht".extraConfig = "deny all;"; "~ /\\.ht".extraConfig = "deny all;";
}; };
@ -533,35 +532,43 @@ in
users.users."${config.services.caddy.user}" = lib.mkIf (cfg.proxy.server == "caddy") { users.users."${config.services.caddy.user}" = lib.mkIf (cfg.proxy.server == "caddy") {
extraGroups = [ cfg.group ]; extraGroups = [ cfg.group ];
}; };
services.caddy = lib.mkIf (cfg.proxy.server == "caddy") { services.caddy =
enable = true; let
proto_hostname = (
if (cfg.hostname == "localhost")
then (cfg.hostname + ":80")
else (cfg.hostname + ":443")
);
in
lib.mkIf (cfg.proxy.server == "caddy") {
enable = true;
globalConfig = lib.mkIf (cfg.hostname == "localhost") '' globalConfig = lib.mkIf (cfg.hostname == "localhost") ''
auto_https disable_redirects auto_https disable_redirects
''; '';
virtualHosts."${cfg.hostname}" = lib.mkMerge [ virtualHosts."${proto_hostname}" = lib.mkMerge [
cfg.proxy.caddyConfig cfg.proxy.caddyConfig
{ {
hostName = lib.mkForce cfg.hostname; hostName = lib.mkForce proto_hostname;
extraConfig = '' extraConfig = ''
encode zstd gzip encode zstd gzip
root * ${invoice-ninja}/public root * ${invoiceninja}/public
php_fastcgi unix/${config.services.phpfpm.pools.invoice-ninja.socket} php_fastcgi unix/${config.services.phpfpm.pools.invoiceninja.socket}
try_files {path} /index.html try_files {path} /index.html
header { header {
Access-Control-Allow-Origin "*" Access-Control-Allow-Origin "*"
Access-Control-Allow-Methods "*" Access-Control-Allow-Methods "*"
Access-Control-Max-Age "0" Access-Control-Max-Age "0"
Access-Control-Allow-Headers "X-API-COMPANY-KEY,X-API-SECRET,X-API-TOKEN,X-API-PASSWORD,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Disposition,Content-Type,Range,X-CSRF-TOKEN,X-XSRF-TOKEN,X-LIVEWIRE" Access-Control-Allow-Headers "X-API-COMPANY-KEY,X-API-SECRET,X-API-TOKEN,X-API-PASSWORD,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Disposition,Content-Type,Range,X-CSRF-TOKEN,X-XSRF-TOKEN,X-LIVEWIRE"
Access-Control-Expose-Headers "X-APP-VERSION,X-MINIMUM-CLIENT-VERSION,X-CSRF-TOKEN,X-XSRF-TOKEN,X-LIVEWIRE" Access-Control-Expose-Headers "X-APP-VERSION,X-MINIMUM-CLIENT-VERSION,X-CSRF-TOKEN,X-XSRF-TOKEN,X-LIVEWIRE"
Access-Control-Allow-Credentials false Access-Control-Allow-Credentials false
} }
file_server file_server
''; '';
} }
]; ];
}; };
services.mysql = lib.mkIf (cfg.database.createLocally) { services.mysql = lib.mkIf (cfg.database.createLocally) {
enable = lib.mkDefault true; enable = lib.mkDefault true;
@ -577,33 +584,33 @@ in
]; ];
}; };
systemd.services.phpfpm-invoice-ninja.after = [ "invoice-ninja-data-setup.service" ]; systemd.services.phpfpm-invoiceninja.after = [ "invoiceninja-data-setup.service" ];
systemd.services.phpfpm-invoice-ninja.requires = [ systemd.services.phpfpm-invoiceninja.requires = [
"invoice-ninja-data-setup.service" "invoiceninja-data-setup.service"
] ++ lib.optional cfg.database.createLocally "mysql.service"; ] ++ lib.optional cfg.database.createLocally "mysql.service";
# Ensure chromium is available # Ensure chromium is available
systemd.services.phpfpm-invoice-ninja.path = extraPrograms; systemd.services.phpfpm-invoiceninja.path = extraPrograms;
systemd.services.invoice-ninja-queue-worker = { systemd.services.invoiceninja-queue-worker = {
description = "Invoice Ninja periodic tasks"; description = "Invoice Ninja periodic tasks";
after = [ "invoice-ninja-data-setup.service" ]; after = [ "invoiceninja-data-setup.service" ];
requires = [ "phpfpm-invoice-ninja.service" ]; requires = [ "phpfpm-invoiceninja.service" ];
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
reloadTriggers = [ invoice-ninja ]; reloadTriggers = [ invoiceninja ];
reload = "${invoice-ninja-manage}/bin/invoice-ninja-manage queue:restart"; reload = "${invoiceninja-manage}/bin/invoiceninja-manage queue:restart";
serviceConfig = { serviceConfig = {
ExecStart = "${invoice-ninja-manage}/bin/invoice-ninja-manage queue:work ${ ExecStart = "${invoiceninja-manage}/bin/invoiceninja-manage queue:work ${
lib.strings.optionalString (cfg.settings.QUEUE_CONNECTION == "redis") "redis" lib.strings.optionalString (cfg.settings.QUEUE_CONNECTION == "redis") "redis"
}"; }";
User = user; User = user;
Group = group; Group = group;
StateDirectory = lib.mkIf (cfg.dataDir == "/var/lib/invoice-ninja") "invoice-ninja"; StateDirectory = lib.mkIf (cfg.dataDir == "/var/lib/invoiceninja") "invoiceninja";
Restart = "always"; Restart = "always";
}; };
}; };
systemd.services.invoice-ninja-data-setup = { systemd.services.invoiceninja-data-setup = {
description = "Invoice Ninja setup: migrations, environment file update, cache reload, data changes"; description = "Invoice Ninja setup: migrations, environment file update, cache reload, data changes";
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
after = lib.optional cfg.database.createLocally "mysql.service"; after = lib.optional cfg.database.createLocally "mysql.service";
@ -612,7 +619,7 @@ in
with pkgs; with pkgs;
[ [
bash bash
invoice-ninja-manage invoiceninja-manage
rsync rsync
config.services.mysql.package config.services.mysql.package
] ]
@ -622,7 +629,7 @@ in
Type = "oneshot"; Type = "oneshot";
User = user; User = user;
Group = group; Group = group;
StateDirectory = lib.mkIf (cfg.dataDir == "/var/lib/invoice-ninja") "invoice-ninja"; StateDirectory = lib.mkIf (cfg.dataDir == "/var/lib/invoiceninja") "invoiceninja";
StateDirectoryMode = "0750"; StateDirectoryMode = "0750";
LoadCredential = [ LoadCredential = [
"env-secrets:${cfg.secretFile}" "env-secrets:${cfg.secretFile}"
@ -645,38 +652,33 @@ in
cat "$CREDENTIALS_DIRECTORY/env-secrets" >> ${cfg.dataDir}/.env cat "$CREDENTIALS_DIRECTORY/env-secrets" >> ${cfg.dataDir}/.env
# Link the static storage (package provided) to the runtime storage # Link the static storage (package provided) to the runtime storage
# Necessary for cities.json and static images. rsync -av --no-perms ${invoiceninja}/storage-static/ ${cfg.dataDir}/storage
rsync -av --no-perms ${invoice-ninja}/storage-static/ ${cfg.dataDir}/storage
# Link the static vendor/bin (package provided) to the runtime vendor/bin
# Necessary for cities.json and static images.
rsync -av --no-perms ${invoice-ninja}/vendor/bin-static/ ${cfg.dataDir}/vendor/bin
# Link the app.php in the runtime folder. # Link the app.php in the runtime folder.
# We cannot link the cache folder only because bootstrap folder needs to be writeable. # 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 ln -sf ${invoiceninja}/bootstrap-static/app.php ${cfg.runtimeDir}/app.php
# Perform the first migration # Perform the first migration
[[ ! -f ${cfg.dataDir}/.initial-migration ]] && invoice-ninja-manage migrate --force && touch ${cfg.dataDir}/.initial-migration [[ ! -f ${cfg.dataDir}/.initial-migration ]] && invoiceninja-manage migrate --force && touch ${cfg.dataDir}/.initial-migration
# Seed database with records # Seed database with records
# Necessary for languages, currencies, countries, etc. # Necessary for languages, currencies, countries, etc.
[[ ! -f ${cfg.dataDir}/.db-seeded ]] && invoice-ninja-manage db:seed --force && touch ${cfg.dataDir}/.db-seeded [[ ! -f ${cfg.dataDir}/.db-seeded ]] && invoiceninja-manage db:seed --force && touch ${cfg.dataDir}/.db-seeded
# Create Invoice Ninja admin account # Create Invoice Ninja admin account
[[ (! -f ${cfg.dataDir}/.admin-created) && (${ [[ (! -f ${cfg.dataDir}/.admin-created) && (${
if cfg.adminAccount.createAdmin then "true" else "false" if cfg.adminAccount.enable then "true" else "false"
} == "true") ]] \ } == "true") ]] \
&& invoice-ninja-manage ninja:create-account --email=${cfg.adminAccount.email} --password=$(cat $CREDENTIALS_DIRECTORY/admin-pass) \ && invoiceninja-manage ninja:create-account --email=${cfg.adminAccount.email} --password=$(cat $CREDENTIALS_DIRECTORY/admin-pass) \
&& touch ${cfg.dataDir}/.admin-created && touch ${cfg.dataDir}/.admin-created
invoice-ninja-manage route:cache invoiceninja-manage route:cache
invoice-ninja-manage view:cache invoiceninja-manage view:cache
invoice-ninja-manage config:cache invoiceninja-manage config:cache
''; '';
}; };
systemd.tmpfiles.settings."10-invoice-ninja" = systemd.tmpfiles.settings."10-invoiceninja" =
lib.attrsets.genAttrs lib.attrsets.genAttrs
[ [
cfg.dataDir cfg.dataDir

View File

@ -3,24 +3,23 @@
, openssl , openssl
, writers , writers
, fetchFromGitHub , fetchFromGitHub
, dataDir ? "/var/lib/invoice-ninja" , dataDir ? "/var/lib/invoiceninja"
, runtimeDir ? "/run/invoice-ninja" , runtimeDir ? "/run/invoiceninja"
}: }:
php.buildComposerProject (finalAttrs: { php.buildComposerProject (finalAttrs: {
pname = "invoice-ninja"; pname = "invoiceninja";
version = "5.11.4"; version = "5.12.13";
src = fetchFromGitHub { src = fetchFromGitHub {
owner = "invoiceninja"; owner = "invoiceninja";
repo = "invoiceninja"; repo = "invoiceninja";
rev = "v${finalAttrs.version}"; rev = "v${finalAttrs.version}";
hash = "sha256-F2LICmDflAM3qIqmE9n3tAbOpSttEY4pQxPWNWZMWMA="; hash = "sha256-/+dmZUxDeC33bBuM2oZwU9wOVtJY0X5/dkhlpbfLkYg=";
}; };
vendorHash = "sha256-RA7IkPXz1HdqQAyB/VIbYg3BmCnlJKLxIVtODIRmZxg="; vendorHash = "sha256-NzFOh3XpKC3Ia1Ns9I6xN9N6y1F5dFSEk7bxq/eKZIc=";
# Patch sources for more restrictive permissions
patches = [ patches = [
./disable-react-for-admin.patch ./disable-react-for-admin.patch
]; ];
@ -38,13 +37,11 @@ php.buildComposerProject (finalAttrs: {
# Move static contents for the NixOS module to pick it up, if needed. # Move static contents for the NixOS module to pick it up, if needed.
mv $out/bootstrap $out/bootstrap-static mv $out/bootstrap $out/bootstrap-static
mv $out/storage $out/storage-static mv $out/storage $out/storage-static
mv $out/vendor/bin $out/vendor/bin-static
# Link NixOS module files to derivation output # Link NixOS module files to derivation output
ln -s ${dataDir}/.env $out/.env ln -s ${dataDir}/.env $out/.env
ln -s ${dataDir}/storage $out/ ln -s ${dataDir}/storage $out/
ln -s ${dataDir}/storage/app/public $out/public/storage ln -s ${dataDir}/storage/app/public $out/public/storage
ln -s ${dataDir}/vendor/bin $out/vendor
ln -s ${runtimeDir} $out/bootstrap ln -s ${runtimeDir} $out/bootstrap
''; '';

View File

@ -1,26 +0,0 @@
{ pkgs ? import <nixpkgs> { config.allowUnfree = true; }
, lib ? import <nixpkgs/lib>
}:
let
# Helper script to generate an APP_KEY for .env
generate-invoice-ninja-app-key = pkgs.writers.writeBashBin "generate-laravel-key" ''
echo "APP_KEY=base64:$(${pkgs.openssl}/bin/openssl rand -base64 32)"
'';
# Invoice Ninja derivation
# Add to buildInputs to test in nix-shell environment
invoice-ninja = pkgs.callPackage ./default.nix {
inherit lib;
php = pkgs.php;
openssl = pkgs.openssl;
fetchFromGitHub = pkgs.fetchFromGitHub;
};
in
pkgs.mkShell {
buildInputs = [
generate-invoice-ninja-app-key
pkgs.nixpkgs-fmt
];
}

30
tests/default.nix Normal file
View File

@ -0,0 +1,30 @@
{ modulesPath, ... }:
{
imports = [
(modulesPath + "/profiles/qemu-guest.nix")
../nixos-module/invoiceninja.nix
];
system.stateVersion = "24.11";
nixpkgs.config.allowUnfree = true;
users.users.test = {
isNormalUser = true;
extraGroups = [ "wheel" ];
initialPassword = "test";
};
services.invoiceninja = {
enable = true;
proxy.server = "caddy";
adminAccount.passwordFile = ./invoice_ninja_test_password;
secretFile = ./test.env;
};
networking.firewall.enable = false;
services.resolved.enable = true;
}

View File

@ -0,0 +1 @@
password

View File

@ -1,53 +0,0 @@
{ modulesPath, ... }:
{
imports = [
(modulesPath + "/profiles/qemu-guest.nix")
../invoice-ninja.nix
];
system.stateVersion = "24.05";
nixpkgs.config.allowUnfree = true;
environment.etc."msmtp-password" = {
enable = true;
user = "invoiceninja";
group = "invoiceninja";
mode = "0440";
text = ''
3t5h638t3a7y7275
'';
};
users.users.test = {
isNormalUser = true;
extraGroups = [ "wheel" ];
initialPassword = "test";
};
services.invoice-ninja = {
enable = true;
database.createLocally = true;
webserver.caddy.enable = true;
webserver.nginx.enable = false;
mail.mailFromName = "Andrew Bryant";
adminEmail = "billing@allpawcare.com";
msmtp.accounts.invoice-ninja = {
auth = true;
tls = true;
tls_starttls = false;
from = "billing@allpawcare.com";
host = "smtp.fastmail.com";
port = 465;
user = "awkawb@awkawb.cloud";
passwordeval = "cat /etc/msmtp-password";
};
secretFile = ./test-secrets.env;
};
networking.firewall.enable = false;
services.resolved.enable = true;
}

View File

@ -1 +0,0 @@
test