feat(fava)!: Improve ledger configurability
This contains the following fixes and improvements for fava config:
- Changing the config now takes effect on old files, as well as new ones
- Options have been introduced to allow setting arbitrary beancount and
fava config, rather than just the title
Change-Id: Ia827ee9646df6b18491a1257ec1a2582e4d8e971
Reviewed-on: https://git.clicks.codes/c/Infra/NixFiles/+/793
Tested-by: Skyler Grey <minion@clicks.codes>
Reviewed-by: Samuel Shuert <coded@clicks.codes>
diff --git a/modules/nixos/clicks/services/fava/default.nix b/modules/nixos/clicks/services/fava/default.nix
index 30014c3..fb707fd 100644
--- a/modules/nixos/clicks/services/fava/default.nix
+++ b/modules/nixos/clicks/services/fava/default.nix
@@ -23,13 +23,98 @@
description = "Disable warnings about disrecommended or possibly unsafe authentication configurations";
};
accounts = lib.mkOption {
- type = lib.home-manager.hm.types.dagOf lib.types.str;
+ type = lib.home-manager.hm.types.dagOf (lib.types.submodule (submodule: let
+ filename = let
+ optionPath = submodule.options._module.args.loc;
+ len = lib.lists.length optionPath;
+ index = len - 4; # Ends ["data" "_module" "args"] in a DAG
+ in lib.lists.elemAt optionPath index;
+ in {
+ options = {
+ name = lib.mkOption {
+ type = lib.types.str;
+ description = "The friendly name of the ledger";
+ example = "Clicks Codes";
+ };
+ favaExtraOptions = lib.mkOption {
+ type = lib.types.attrsOf (lib.types.nullOr lib.types.str);
+ description = "Extra options to set on this ledger for fava. See https://fava.pythonanywhere.com/example-beancount-file/help/options/ for a list of valid options. These mostly control fava UI, etc. Setting an option to null means that it will be given without a value";
+ example = {
+ invert-income-liabilities-equity = true;
+ };
+ default = {};
+ };
+ beancountExtraOptions = lib.mkOption {
+ type = lib.types.attrsOf lib.types.str;
+ description = "Extra options to set on this ledger for beancount. See https://beancount.github.io/docs/beancount_options_reference.html for a list of valid options. These mostly control the way beancount interprets numbers, account names, etc.";
+ example = {
+ long_string_maxlines = "128";
+ };
+ };
+ extraConfig = lib.mkOption {
+ type = lib.types.str;
+ description = "Extra lines to be added to the end of the beancount config file verbatim. Probably you want these to contain configuration, but it's not required - for example it would be valid to open accounts here to force those accounts to be opened";
+ example = ''
+ ; lines to be added to the beancount config file directly...
+ '';
+ default = "";
+ };
+ mainFile = lib.mkOption {
+ type = lib.types.str;
+ readOnly = true;
+ internal = true;
+ description = "The file that you will write your transactions, etc. in";
+ default = "/var/lib/private/fava/${filename}.beancount";
+ };
+ configFile = lib.mkOption {
+ type = lib.types.str;
+ readOnly = true;
+ internal = true;
+ description = "The file with all of the beancount/fava/etc. options compiled";
+ };
+ };
+ config = {
+ beancountExtraOptions.title = lib.mkDefault submodule.config.name;
+
+ configFile = builtins.toString (let
+ generateFavaOption = name: value:
+ if value == null
+ then ''1970-01-01 custom "fava-option" "${name}"''
+ else ''1970-01-01 custom "fava-option" "${name}" "${value}"'';
+ favaOptions = lib.pipe submodule.config.favaExtraOptions [
+ (lib.attrsets.mapAttrsToList generateFavaOption)
+ (lib.strings.concatStringsSep "\n")
+ ];
+
+ generateBeancountOption = name: value: ''option "${name}" "${value}"'';
+ beancountOptions = lib.pipe submodule.config.beancountExtraOptions [
+ (lib.attrsets.mapAttrsToList generateBeancountOption)
+ (lib.strings.concatStringsSep "\n")
+ ];
+ in pkgs.writeText "${filename}-config" ''
+ ${favaOptions}
+ ${beancountOptions}
+ ${submodule.config.extraConfig}
+
+ include "${submodule.config.mainFile}"
+ '');
+ };
+ }));
example = {
- "minion" = "Skyler Grey";
- "clicks" = "Clicks Codes";
- "coded" = "Samuel Shuert";
+ "clicks" = lib.home-manager.hm.dag.entryAnywhere {
+ name = "Clicks Codes";
+ favaExtraOptions = {
+ invert-income-liabilities-equity = true;
+ };
+ beancountExtraOptions = {
+ long_string_maxlines = "128";
+ };
+ extraConfig = ''
+ ; lines to be added to the beancount config file directly...
+ '';
+ };
};
- description = "A mapping of file names to ledger titles. Titles will not override any existing ledgers, they will only be added to new ones";
+ description = "A mapping of file names to ledger settings. Ledger settings will not be written to ledgers that are already created, although accompanying files will still be written";
};
addr = lib.mkOption {
type = lib.types.str;
@@ -75,29 +160,18 @@
};
systemd.services.fava = let
- getFilePath = name: "/var/lib/private/fava/${name}.beancount";
- # Cannot use $STATE_DIRECTORY here as:
- # (1) it's not supported via fava environment variables
- # (2) it is a symlink and fava does not like that
- attrNamesToPaths = accounts: lib.attrsets.mapAttrs' (name: value: {
- name = getFilePath name;
- inherit value;
- }) accounts;
-
sortedAccounts = (lib.home-manager.hm.dag.topoSort cfg.accounts).result;
fileNames = lib.trivial.pipe sortedAccounts [
- (map (account: account.name))
- (map getFilePath)
+ (map (account: account.data.configFile))
(lib.strings.concatStringsSep " ")
];
- accountToCreationScript = { name, data }: let
- path = getFilePath name;
+ accountToCreationScript = { data, ... }: let
in ''
- ${pkgs.coreutils}/bin/mkdir -p "$(${pkgs.coreutils}/bin/dirname "${path}")"
- if [ ! -f "${path}" ]; then
- ${pkgs.coreutils}/bin/echo 'option "title" "${data}"' >> ${path}
+ ${pkgs.coreutils}/bin/mkdir -p "$(${pkgs.coreutils}/bin/dirname "${data.mainFile}")"
+ if [ ! -f "${data.mainFile}" ]; then
+ ${pkgs.coreutils}/bin/echo '1970-01-01 custom "fava-option" "default-file"' >> ${data.mainFile}
fi
'';
diff --git a/systems/x86_64-linux/teal/default.nix b/systems/x86_64-linux/teal/default.nix
index 2a9b99b..d09bb7f 100644
--- a/systems/x86_64-linux/teal/default.nix
+++ b/systems/x86_64-linux/teal/default.nix
@@ -164,10 +164,21 @@
enable = true;
tailscaleAuth = true;
accounts = {
- "clicks" = lib.home-manager.hm.dag.entryAnywhere "Clicks Codes";
- "coded" = lib.home-manager.hm.dag.entryBetween [ "testing" ] [ "clicks" ] "Samuel Shuert";
- "minion" = lib.home-manager.hm.dag.entryBetween [ "testing" ] [ "clicks" ] "Skyler Grey";
- "testing" = lib.home-manager.hm.dag.entryAfter [ "clicks" ] "Test Data - May Be Wiped At Any Time";
+ "clicks" = lib.home-manager.hm.dag.entryAnywhere {
+ name = "Clicks Codes";
+ beancountExtraOptions.operating_currency = "GBP";
+ };
+ "coded" = lib.home-manager.hm.dag.entryBetween [ "testing" ] [ "clicks" ] {
+ name = "Samuel Shuert";
+ beancountExtraOptions.operating_currency = "USD";
+ };
+ "minion" = lib.home-manager.hm.dag.entryBetween [ "testing" ] [ "clicks" ] {
+ name = "Skyler Grey";
+ beancountExtraOptions.operating_currency = "GBP";
+ };
+ "testing" = lib.home-manager.hm.dag.entryAfter [ "clicks" ] {
+ name = "Test Data - May Be Wiped At Any Time";
+ };
};
domain = "fava.clicks.codes";
};